Merge origin/master into fix/2544

Resolve formatter_test.cc conflict by keeping MacroBeforeCloseParenFormatEquivalent
alongside EndElseIfWithEOLCommentConverges from merged #2541.
diff --git a/verible/verilog/analysis/verilog-equivalence.cc b/verible/verilog/analysis/verilog-equivalence.cc
index f8a47d0..026c197 100644
--- a/verible/verilog/analysis/verilog-equivalence.cc
+++ b/verible/verilog/analysis/verilog-equivalence.cc
@@ -167,11 +167,21 @@
   DiffStatus diff_status = DiffStatus::kEquivalent;
   auto recursive_comparator = [&](const TokenSequence::const_iterator l,
                                   const TokenSequence::const_iterator r) {
+    // Some token enums differ only by surrounding whitespace (e.g. whether a
+    // macro or ')' ends a line). Treat those pairs as matching enums when the
+    // spelling is unchanged so FormatEquivalent tolerates re-wrapping.
+    const bool whitespace_dependent_enum_match =
+        ((l->token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
+          r->text() == ")") ||
+         (r->token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
+          l->text() == ")") ||
+         ((l->token_enum() == verilog_tokentype::MacroIdentifier ||
+           l->token_enum() == verilog_tokentype::MacroIdItem) &&
+          (r->token_enum() == verilog_tokentype::MacroIdentifier ||
+           r->token_enum() == verilog_tokentype::MacroIdItem) &&
+          l->text() == r->text()));
     if (l->token_enum() != r->token_enum() &&
-        !((l->token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
-           r->text() == ")") ||
-          (r->token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
-           l->text() == ")"))) {
+        !whitespace_dependent_enum_match) {
       if (errstream != nullptr) {
         *errstream << "Mismatched token enums.  got: ";
         token_printer(*l, *errstream);
@@ -258,6 +268,16 @@
              (r.text() == ")"))) {
           return true;
         }
+        // MacroIdentifier vs MacroIdItem depends only on whether the macro
+        // ends the line (see POST_MACRO_ID in verilog.lex). Spelling-equal
+        // macros are format-equivalent across that reclassification.
+        if ((l.token_enum() == verilog_tokentype::MacroIdentifier ||
+             l.token_enum() == verilog_tokentype::MacroIdItem) &&
+            (r.token_enum() == verilog_tokentype::MacroIdentifier ||
+             r.token_enum() == verilog_tokentype::MacroIdItem) &&
+            l.text() == r.text()) {
+          return true;
+        }
         return l.EquivalentWithoutLocation(r);
       },
       errstream);
diff --git a/verible/verilog/analysis/verilog-equivalence_test.cc b/verible/verilog/analysis/verilog-equivalence_test.cc
index 90b0ee1..1cb9436 100644
--- a/verible/verilog/analysis/verilog-equivalence_test.cc
+++ b/verible/verilog/analysis/verilog-equivalence_test.cc
@@ -255,6 +255,21 @@
   }
 }
 
+// MacroIdentifier vs MacroIdItem depends on whether the macro ends the line.
+TEST(FormatEquivalentTest, EquivalenceOfMacroIdentifierAndMacroIdItem) {
+  const char *kSameSpelling[] = {
+      "assign x = f(`TOKEN);\n",
+      "assign x = f(\n`TOKEN\n);\n",
+  };
+  ExpectCompareWithErrstream(FormatEquivalent, DiffStatus::kEquivalent,
+                             kSameSpelling[0], kSameSpelling[1]);
+
+  // Different macro names remain different.
+  ExpectCompareWithErrstream(FormatEquivalent, DiffStatus::kDifferent,
+                             "assign x = f(`TOKEN);\n",
+                             "assign x = f(`OTHER);\n");
+}
+
 TEST(FormatEquivalentTest, DiagnosticMismatch) {
   const char *kTestCases[] = {
       "module foo;\n",
diff --git a/verible/verilog/formatting/formatter_test.cc b/verible/verilog/formatting/formatter_test.cc
index dce9f05..5501c58 100644
--- a/verible/verilog/formatting/formatter_test.cc
+++ b/verible/verilog/formatting/formatter_test.cc
@@ -19380,6 +19380,24 @@
   }
 }
 
+// Regression for https://github.com/chipsalliance/verible/issues/2544:
+// Wrapping a $bits(...)'(...) cast may leave `MACRO at EOL, reclassifying
+// MacroIdentifier as MacroIdItem. FormatEquivalent must accept that, and
+// formatting must still pass verification.
+TEST(FormatterEndToEndTest, MacroBeforeCloseParenFormatEquivalent) {
+  static constexpr std::string_view kInput =
+      "module m;\n"
+      "  assign result_value = $bits(result_value)'( "
+      "compare_bytes(input_data[DATA_WIDTH_INT-1:0], "
+      "input_datak[STROBE_WIDTH_INT-1:0], `TOKEN_BYTE) );\n"
+      "endmodule\n";
+  FormatStyle style;
+  std::ostringstream stream;
+  const auto status = FormatVerilog(kInput, "<filename>", style, stream);
+  EXPECT_OK(status) << status.message();
+  EXPECT_THAT(stream.str(), testing::HasSubstr("`TOKEN_BYTE"));
+}
+
 // Regression for https://github.com/chipsalliance/verible/issues/2540:
 // Trailing EOL comment after `end` before `else if` must not change whether
 // the else-if assignment stays on one line across re-format (convergence).