From 15919cdaa08d138fca41ee755f56951acea9d80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=98=E5=85=B8?= Date: Sun, 9 Aug 2026 09:44:28 +0800 Subject: [PATCH] fix: render error caret for the last character of the parsing context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ParsingException.getMessage() guards the caret rendering with `column < context.length()`, but `column` is 1-indexed (the caret is drawn at position `column - 1`), so when the error is at the last character (column == context.length()), the caret was not rendered. Use `<=` so the caret is shown for the last character too, while still rejecting positions past the end of the context. A comment notes the 1-indexing so the boundary is not flipped back by mistake. Closes #625 Signed-off-by: 付典 --- .../configurate/loader/ParsingException.java | 4 ++- .../loader/ParsingExceptionTest.java | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/org/spongepowered/configurate/loader/ParsingExceptionTest.java diff --git a/core/src/main/java/org/spongepowered/configurate/loader/ParsingException.java b/core/src/main/java/org/spongepowered/configurate/loader/ParsingException.java index 1c19b53b5..80a38ef83 100644 --- a/core/src/main/java/org/spongepowered/configurate/loader/ParsingException.java +++ b/core/src/main/java/org/spongepowered/configurate/loader/ParsingException.java @@ -175,7 +175,9 @@ public int column() { if (this.context != null) { message.append(System.lineSeparator()).append(this.context); - if (this.column >= 0 && this.column < this.context.length()) { + // column is 1-indexed: column == context.length() points at the last character, + // so the caret drawn at position (column - 1) stays within the context bounds. + if (this.column >= 0 && this.column <= this.context.length()) { message.append(System.lineSeparator()); if (this.column > 0) { final char[] spaces = new char[this.column - 1]; diff --git a/core/src/test/java/org/spongepowered/configurate/loader/ParsingExceptionTest.java b/core/src/test/java/org/spongepowered/configurate/loader/ParsingExceptionTest.java new file mode 100644 index 000000000..c89892238 --- /dev/null +++ b/core/src/test/java/org/spongepowered/configurate/loader/ParsingExceptionTest.java @@ -0,0 +1,36 @@ +package org.spongepowered.configurate.loader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class ParsingExceptionTest { + + // column is 1-indexed, so column == context.length() points at the last char. + // The caret must be rendered there too. Previously the guard was + // `column < context.length()`, which dropped the caret for the last char (#625). + @Test + void caretRenderedForLastColumn() { + final String context = "hello"; // length 5 + final String message = new ParsingException(1, context.length(), context, "err", null).getMessage(); + + // The caret sits under the last character: (length - 1) leading spaces, then '^'. + final int caretIndex = message.indexOf('^'); + assertTrue(caretIndex >= 0, "a caret should be rendered for the last column"); + int leadingSpaces = 0; + for (int i = caretIndex - 1; i >= 0 && message.charAt(i) == ' '; i--) { + leadingSpaces++; + } + assertEquals(context.length() - 1, leadingSpaces, "caret should be aligned under the last character"); + } + + @Test + void caretNotRenderedBeyondContext() { + final String context = "hello"; + final String message = new ParsingException(1, context.length() + 1, context, "err", null).getMessage(); + assertFalse(message.contains("^"), "caret should not be rendered past the context"); + } + +}