From 565ee5e0f39162e1d7528efd3e886fb5c4cb723d Mon Sep 17 00:00:00 2001 From: Matthew Orford Date: Mon, 1 Jun 2026 23:38:44 -0400 Subject: [PATCH] Fix origin line numbers after newline separators --- .../typesafe/config/impl/ConfigNodeField.java | 14 ++++++++++++++ .../com/typesafe/config/impl/ConfigParser.java | 16 +++++++++++----- .../typesafe/config/impl/ConfParserTest.scala | 15 +++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigNodeField.java b/config/src/main/java/com/typesafe/config/impl/ConfigNodeField.java index 10ba37429..db9db8b83 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigNodeField.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigNodeField.java @@ -66,6 +66,20 @@ protected Token separator() { return null; } + protected int newlineCountBeforeValue() { + int newlineCount = 0; + for (AbstractConfigNode child : children) { + if (child instanceof AbstractConfigNodeValue) + return newlineCount; + + if (child instanceof ConfigNodeSingleToken + && Tokens.isNewline(((ConfigNodeSingleToken) child).token())) { + newlineCount++; + } + } + return newlineCount; + } + protected List comments() { List comments = new ArrayList(); for (AbstractConfigNode child : children) { diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigParser.java b/config/src/main/java/com/typesafe/config/impl/ConfigParser.java index 1e562ace2..90b5d5d9e 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigParser.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigParser.java @@ -120,6 +120,10 @@ private AbstractConfigValue parseValue(AbstractConfigNodeValue n, List c return v; } + private void advanceLineNumberBeforeValue(ConfigNodeField field) { + lineNumber += field.newlineCountBeforeValue(); + } + private static AbstractConfigObject createValueUnderPath(Path path, AbstractConfigValue value) { // for path foo.bar, we are creating @@ -237,13 +241,14 @@ private AbstractConfigObject parseObject(ConfigNodeObject n) { parseInclude(values, (ConfigNodeInclude)node); lastWasNewline = false; } else if (node instanceof ConfigNodeField) { + ConfigNodeField field = (ConfigNodeField) node; lastWasNewline = false; - Path path = ((ConfigNodeField) node).path().value(); - comments.addAll(((ConfigNodeField) node).comments()); + Path path = field.path().value(); + comments.addAll(field.comments()); // path must be on-stack while we parse the value pathStack.push(path); - if (((ConfigNodeField) node).separator() == Tokens.PLUS_EQUALS) { + if (field.separator() == Tokens.PLUS_EQUALS) { // we really should make this work, but for now throwing // an exception is better than producing an incorrect // result. See @@ -262,12 +267,13 @@ private AbstractConfigObject parseObject(ConfigNodeObject n) { AbstractConfigNodeValue valueNode; AbstractConfigValue newValue; - valueNode = ((ConfigNodeField) node).value(); + valueNode = field.value(); + advanceLineNumberBeforeValue(field); // comments from the key token go to the value token newValue = parseValue(valueNode, comments); - if (((ConfigNodeField) node).separator() == Tokens.PLUS_EQUALS) { + if (field.separator() == Tokens.PLUS_EQUALS) { arrayCount -= 1; List concat = new ArrayList(2); diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala index 2724b495a..f32b7b3b1 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala @@ -878,6 +878,21 @@ class ConfParserTest extends TestUtils { assertEquals("bar", conf.getString("foo")) } + @Test + def valuesAfterSeparatorNewlineHaveCorrectOriginLine() { + val scalar = parseConfig("a=\n42") + assertEquals(2, scalar.getValue("a").origin().lineNumber()) + + val objectAfterEquals = parseConfig("a=\n{\n b=1\n}") + assertEquals(2, objectAfterEquals.getObject("a").origin().lineNumber()) + + val objectAfterColon = parseConfig("a:\n{\n b=1\n}") + assertEquals(2, objectAfterColon.getObject("a").origin().lineNumber()) + + val arrayAfterEquals = parseConfig("a=\n[\n 1\n]") + assertEquals(2, arrayAfterEquals.getList("a").origin().lineNumber()) + } + @Test def acceptMultiPeriodNumericPath() { val conf1 = ConfigFactory.parseString("0.1.2.3=foobar1")