drccrd commented on code in PR #4005:
URL: 
https://github.com/apache/incubator-kie-tools/pull/4005#discussion_r4062312773


##########
packages/drools-lsp/drools-formatter/src/main/java/org/drools/formatter/RhsFormatter.java:
##########
@@ -0,0 +1,517 @@
+/*
+ * 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.drools.formatter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.antlr.v4.runtime.Token;
+import org.drools.drl.parser.antlr4.DRL10Parser;
+
+/**
+ * Formats consequences (then blocks): token-level, since the parser keeps 
them opaque.
+ *
+ * <p>The DRL parser treats {@code then} blocks as opaque text with individual
+ * tokens on a special RHS channel, so formatting works at the token level
+ * rather than the CST level:
+ * <ul>
+ *   <li>{@code emitConsequenceBody} collects visible RHS tokens into
+ *       per-statement lists, splitting only at newlines where paren depth is 0
+ *       (so continuation lines within expanded calls stay together).</li>
+ *   <li>{@code emitRhsStatement} tries the compact (single-line) form first.
+ *       If it exceeds {@code options.lineLength()}, it delegates to
+ *       {@code rhsEmitExpanded}.</li>
+ *   <li>{@code rhsEmitExpanded} uses {@code rhsMarkExpanded} to decide which
+ *       {@code (} groups need expansion: any group whose compact rendering
+ *       (including trailing {@code );}) would exceed the line limit is marked.
+ *       Marked groups get one argument per line with the closing {@code )}
+ *       on its own line at the same indent as the opening.</li>
+ * </ul>
+ */
+final class RhsFormatter {
+  private final Emitter e;
+
+  // Extra depth added when a case/default label is emitted without an inline
+  // consequence (consequence follows on subsequent lines). Reset to 0 when
+  // the next case/default label or closing '}' is encountered.
+  private int caseLabelDepthBump = 0;
+
+  RhsFormatter(Emitter e) {
+    this.e = e;
+  }
+
+  void visitRhs(DRL10Parser.RhsContext ctx) {
+    e.emitHiddenTokensBefore(ctx);
+    e.emit(e.indent() + "then");
+    e.newline();
+    e.depth++;
+
+    emitConsequenceBody(ctx.consequenceBody());
+
+    for (DRL10Parser.NamedConsequenceContext nc : ctx.namedConsequence()) {
+      e.depth--;
+      // named consequence header: "then[name]"
+      String ncText = nc.RHS_NAMED_CONSEQUENCE_THEN().getText();
+      e.emit(e.indent() + ncText);
+      e.newline();
+      e.depth++;
+      emitConsequenceBody(nc.consequenceBody());
+    }
+
+    e.depth--;
+  }
+
+  private void emitConsequenceBody(DRL10Parser.ConsequenceBodyContext ctx) {
+    if (ctx == null || ctx.getChildCount() == 0) return;
+
+    int startIdx = ctx.getStart().getTokenIndex();
+    int stopIdx = ctx.getStop().getTokenIndex();
+
+    // Collect visible RHS tokens into statements. A statement boundary is
+    // a newline where paren depth is 0 (balanced). Newlines inside parens
+    // are continuation lines and belong to the same statement.
+    List<String> stmtTokens = new ArrayList<>();
+    boolean pendingBlankLine = false;
+    int parenDepth = 0;
+    int lastTokenLine = -1;
+
+    for (int i = startIdx; i <= stopIdx; i++) {
+      Token t = e.tokens.get(i);
+      String text = t.getText();
+
+      if (t.getChannel() == Token.HIDDEN_CHANNEL) {
+        if (text.contains("\n") && parenDepth == 0) {
+          if (!stmtTokens.isEmpty()) {
+            emitRhsStatementInBlock(stmtTokens);
+            stmtTokens.clear();
+          }
+          long nlCount = text.chars().filter(c -> c == '\n').count();
+          if (nlCount > 1) pendingBlankLine = true;
+        }
+        // Newlines inside parens are ignored (tokens collected into same 
statement)
+        continue;
+      }
+
+      if (Emitter.isComment(t)) {
+        // A comment sharing its source line with the previous token is a
+        // trailing comment (e.g. "insert( new Foo() ); // audit note") —
+        // flush the statement it trails, then reattach it to that line
+        // instead of dropping it onto a fresh one.
+        boolean trailsPreviousToken = t.getLine() == lastTokenLine && 
parenDepth == 0;
+        if (!stmtTokens.isEmpty() && parenDepth == 0) {
+          emitRhsStatementInBlock(stmtTokens);
+          stmtTokens.clear();
+        }
+        if (trailsPreviousToken) {
+          e.appendToLastLine(" " + text.trim());
+          e.newline();
+        } else {
+          if (pendingBlankLine) { e.blankLine(); pendingBlankLine = false; }
+          e.emit(e.indent() + text.trim());
+          e.newline();
+        }
+        continue;
+      }
+
+      // Track paren depth for statement boundary detection.
+      // Only count () — curly braces are block delimiters (switch, if, for)
+      // and should not suppress newline-based statement splitting.
+      if (text.equals("(")) parenDepth++;
+      else if (text.equals(")")) parenDepth = Math.max(0, parenDepth - 1);
+
+      // case/default keywords at top level act as statement boundaries so
+      // that multiple cases concatenated on one line are split correctly
+      // (e.g. badly-formatted input: "break; case 2: ...").
+      if (isCaseLabelStart(text) && parenDepth == 0 && !stmtTokens.isEmpty()) {
+        emitRhsStatementInBlock(stmtTokens);
+        stmtTokens.clear();
+      }
+
+      if (pendingBlankLine) { e.blankLine(); pendingBlankLine = false; }
+      stmtTokens.add(text);
+      lastTokenLine = t.getLine();
+    }
+    if (!stmtTokens.isEmpty()) {
+      emitRhsStatementInBlock(stmtTokens);
+    }
+
+    e.lastEmittedTokenIndex = Math.max(e.lastEmittedTokenIndex, stopIdx);
+  }
+
+  /**
+   * Wrapper around {@link #emitRhsStatement} that manages brace-depth and
+   * case/default label splitting so that switch blocks are formatted as:
+   * <pre>
+   *   switch( x )
+   *   {
+   *     case 1:
+   *       value = ...; break;
+   *     default:
+   *       value = ...;
+   *   }
+   * </pre>
+   * Rules:
+   * <ul>
+   *   <li>Statement starting with {@code }} → decrement depth before 
emit.</li>
+   *   <li>Statement ending with {@code {} → increment depth after emit.</li>
+   *   <li>Statement starting with {@code case} or {@code default} → split at
+   *       the first top-level {@code :}, emit label at current depth, bump
+   *       depth by 1, emit consequence, restore depth.</li>
+   * </ul>
+   */
+  private void emitRhsStatementInBlock(List<String> toks) {
+    if (toks.isEmpty()) return;
+
+    String first = toks.get(0);
+    String last  = toks.get(toks.size() - 1);
+
+    // Closing brace: undo any case-consequence depth bump, then decrease 
brace indent.
+    if (first.equals("}")) {
+      e.depth -= caseLabelDepthBump;
+      caseLabelDepthBump = 0;
+      e.depth--;

Review Comment:
   fixed with 
[00e196e](https://github.com/apache/incubator-kie-tools/pull/4005/commits/00e196ef32b01260c4fabd595023f09be3539043)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to