Merge origin/master into fix/2605

Resolve the issue-regression conflict by placing the #2605 test after the
macro-equivalence regression, away from other open PR tests.
diff --git a/verible/verilog/formatting/BUILD b/verible/verilog/formatting/BUILD
index 6e4782e..491a5cf 100644
--- a/verible/verilog/formatting/BUILD
+++ b/verible/verilog/formatting/BUILD
@@ -301,6 +301,7 @@
         ":format-style",
         ":formatter",
         ":formatter-test-utils",
+        "//verible/common/formatting:basic-format-style",
         "//verible/common/util:logging",
         "@googletest//:gtest",
         "@googletest//:gtest_main",
diff --git a/verible/verilog/formatting/formatter_issue_regression_test.cc b/verible/verilog/formatting/formatter_issue_regression_test.cc
index 9490a88..de84377 100644
--- a/verible/verilog/formatting/formatter_issue_regression_test.cc
+++ b/verible/verilog/formatting/formatter_issue_regression_test.cc
@@ -91,6 +91,43 @@
   EXPECT_THAT(stream.str(), testing::HasSubstr("`TOKEN_BYTE"));
 }
 
+// Regression for https://github.com/chipsalliance/verible/issues/2605:
+// TIMESCALE_DIRECTIVE's EndOfLineComment handler used yyless(yyleng-1),
+// which left `\r` in the comment token for CRLF files. Emitting that token
+// plus a CRLF terminator produced `\r\r\n` and failed FormatEquivalent.
+TEST(FormatterEndToEndTest, TimescaleCrlfEolComment) {
+  static constexpr FormatterTestCase kTestCases[] = {
+      // Next-line `//` comment after `timescale (the reduced issue case).
+      {"`timescale 1 ps / 1 ps\r\n"
+       "// hello\r\n"
+       "module m;\r\n"
+       "endmodule\r\n",
+       "`timescale 1 ps / 1 ps\r\n"
+       "// hello\r\n"
+       "module m;\r\n"
+       "endmodule\r\n"},
+      // Same-line `//` comment on the `timescale directive.
+      {"`timescale 1 ps / 1 ps // hello\r\n"
+       "module m;\r\n"
+       "endmodule\r\n",
+       "`timescale 1 ps / 1 ps  // hello\r\n"
+       "module m;\r\n"
+       "endmodule\r\n"},
+      // LF control: this path already passed lexical verification.
+      {"`timescale 1 ps / 1 ps\n"
+       "// hello\n"
+       "module m;\n"
+       "endmodule\n",
+       "`timescale 1 ps / 1 ps\n"
+       "// hello\n"
+       "module m;\n"
+       "endmodule\n"},
+  };
+  FormatStyle style;
+  style.line_terminator = verible::LineTerminatorOptionStyle::kAuto;
+  RunFormatterTestCases(style, kTestCases);
+}
+
 // Regression for https://github.com/chipsalliance/verible/issues/2542:
 // Continuation EOL comments after a wrapped assign must keep a stable column
 // across re-format (convergence).
diff --git a/verible/verilog/parser/verilog-lexer_test.cc b/verible/verilog/parser/verilog-lexer_test.cc
index b1f3788..23d8cda 100644
--- a/verible/verilog/parser/verilog-lexer_test.cc
+++ b/verible/verilog/parser/verilog-lexer_test.cc
@@ -2035,6 +2035,24 @@
      {'/', "/"},
      {TK_TimeLiteral, "1ps"},
      {TK_NEWLINE, "\n"}},
+    // Issue #2605: CRLF `//` comments in TIMESCALE_DIRECTIVE must not keep
+    // `\r`.
+    {{DR_timescale, "`timescale"},
+     {TK_SPACE, " "},
+     {TK_TimeLiteral, "1ps"},
+     {'/', "/"},
+     {TK_TimeLiteral, "1ps"},
+     {TK_NEWLINE, "\r\n"},
+     {TK_EOL_COMMENT, "// hello"},
+     {TK_NEWLINE, "\r\n"}},
+    {{DR_timescale, "`timescale"},
+     {TK_SPACE, " "},
+     {TK_TimeLiteral, "1ps"},
+     {'/', "/"},
+     {TK_TimeLiteral, "1ps"},
+     {TK_SPACE, " "},
+     {TK_EOL_COMMENT, "// hello"},
+     {TK_NEWLINE, "\r\n"}},
     // TODO(b/134180314): lexer current drops tokens from pragma sections,
     // but instead emit the tokens and filter them out.
     // {{DR_pragma, "`pragma"}, " fragma", {TK_NEWLINE, "\n"}},
diff --git a/verible/verilog/parser/verilog.lex b/verible/verilog/parser/verilog.lex
index 759fcc2..5c4cf3b 100644
--- a/verible/verilog/parser/verilog.lex
+++ b/verible/verilog/parser/verilog.lex
@@ -990,7 +990,13 @@
     return TK_COMMENT_BLOCK;
   }
   {EndOfLineComment} {
-    yyless(yyleng-1);  /* return \n to input stream */
+    // Match IN_EOL_COMMENT: CRLF is a two-character terminator. Using
+    // yyless(yyleng-1) left `\r` in the comment token, so the formatter
+    // emitted `\r\r\n` after `timescale comments (issue #2605, PR #2371).
+    if (yyleng >= 2 && yytext[yyleng - 2] == '\r')
+      yyless(yyleng - 2);  /* return \r\n to input stream */
+    else
+      yyless(yyleng - 1);  /* return \n to input stream */
     UpdateLocation();
     return TK_EOL_COMMENT;
   }