Skip to content

Commit edccb19

Browse files
committed
Merge remote-tracking branch 'tim/master'
2 parents bf75a63 + c6388b3 commit edccb19

5 files changed

Lines changed: 77 additions & 3 deletions

File tree

format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,15 @@ protected void loadInternal(final CommentedConfigurationNode node, final Buffere
269269
// the constructor needs ConfigurationOptions for the to be created nodes
270270
// and since it's a thread-local, this won't cause any issues
271271
this.constructor.get().options = node.options();
272-
node.from(this.yaml.get().load(reader));
272+
273+
@Nullable CommentedConfigurationNode loaded = this.yaml.get().load(reader);
274+
// when a file exists but is empty (or if the file only exists of comments), the first event will be StreamEnd.
275+
// getSingleNode will return null, getSingleData uses the Constructor of Tag Null, which just returns null.
276+
// So we have to map null to an empty root node.
277+
if (loaded == null) {
278+
loaded = CommentedConfigurationNode.root(node.options());
279+
}
280+
node.from(loaded);
273281
}
274282

275283
@Override

format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.yaml.snakeyaml.nodes.NodeTuple;
3232
import org.yaml.snakeyaml.nodes.ScalarNode;
3333
import org.yaml.snakeyaml.nodes.SequenceNode;
34+
import org.yaml.snakeyaml.nodes.Tag;
3435

3536
import java.util.Collection;
3637
import java.util.Collections;
@@ -61,6 +62,17 @@ protected Object constructObjectNoCheck(final Node yamlNode) {
6162
//noinspection DataFlowIssue guarenteed NonNull by getSingleData, which load(Reader) uses
6263
final CommentedConfigurationNode node = CommentedConfigurationNode.root(this.options);
6364

65+
// alright, let's first check some interesting behaviour.
66+
// When you have a file with only empty lines (has to be at least two),
67+
// a Tag of the type COMMENT will be executed.
68+
// It'll have a blockComment of the type empty line.
69+
// This is unlike the behaviour of an empty file and a file with only comments,
70+
// because both won't be seen as an object and will instead return null by Yaml#load.
71+
// Let's filter out the multi-empty-lines behaviour and return an empty node instead.
72+
if (yamlNode.getTag() == Tag.COMMENT) {
73+
return node;
74+
}
75+
6476
if (yamlNode.getNodeId() == NodeId.mapping) {
6577
// make sure to mark it as a map type, even if the map itself is empty
6678
node.raw(Collections.emptyMap());

format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.nio.charset.StandardCharsets;
3636
import java.nio.file.Files;
3737
import java.nio.file.Path;
38+
import java.nio.file.Paths;
3839
import java.util.ArrayList;
3940
import java.util.Arrays;
4041
import java.util.Collections;
@@ -108,6 +109,57 @@ void testWriteBasicFile(final @TempDir Path tempDir) throws IOException {
108109
assertContentsSame(this.resource("write-expected.yml"), target);
109110
}
110111

112+
@Test
113+
void testNonExistentFile() throws IOException {
114+
final Path path = Paths.get("non-existent-file.yml");
115+
final YamlConfigurationLoader loader = YamlConfigurationLoader.builder().path(path).build();
116+
117+
final ConfigurationNode actual = loader.load();
118+
assertEquals(CommentedConfigurationNode.root(), actual);
119+
}
120+
121+
@Test
122+
void testEmptyFile(final @TempDir Path tempDir) throws IOException {
123+
// an empty file should be seen as empty node, by default SnakeYaml returns null for loading an empty file.
124+
// Check if it correctly gets mapped to an empty node.
125+
126+
// Configurate's Spotless settings require an empty row after the last row (so essentially making the file
127+
// multiline), to bypass this we write an empty temp file and read that.
128+
final Path emptyFile = tempDir.resolve("empty-file.yml");
129+
Files.write(emptyFile, Collections.emptyList());
130+
131+
final YamlConfigurationLoader loader = YamlConfigurationLoader.builder().path(emptyFile).build();
132+
133+
final ConfigurationNode actual = loader.load();
134+
assertEquals(CommentedConfigurationNode.root(), actual);
135+
}
136+
137+
@Test
138+
void testEmptyFileMultiline() throws IOException {
139+
// a multiline file has at least some data (the fact that it has multiple lines), which is why it's handled
140+
// differently inside SnakeYaml. This is seen as a mapping node with the Tag COMMENT.
141+
142+
final URL url = this.resource("no-data-multiline.yml");
143+
final YamlConfigurationLoader loader = YamlConfigurationLoader.builder().url(url).build();
144+
145+
final ConfigurationNode actual = loader.load();
146+
assertEquals(CommentedConfigurationNode.root(), actual);
147+
}
148+
149+
@Test
150+
void testEmptyFileOnlyComments() throws IOException {
151+
// just like 'empty file' and 'empty file multiline', this is kind of a weird situation.
152+
// In SnakeYaml, when the file exists of just comments, the first event will be StreamEnd.
153+
// getSingleNode will return null, getSingleData uses the Constructor of Tag Null, which just returns null.
154+
// Make sure that we return an empty node there.
155+
156+
final URL url = this.resource("no-data-just-comments.yml");
157+
final YamlConfigurationLoader loader = YamlConfigurationLoader.builder().url(url).build();
158+
159+
final ConfigurationNode actual = loader.load();
160+
assertEquals(CommentedConfigurationNode.root(), actual);
161+
}
162+
111163
@Test
112164
void testReadComments() throws IOException {
113165
final ConfigurationNode expected = CommentedConfigurationNode.root(n ->
@@ -123,8 +175,7 @@ void testReadComments() throws IOException {
123175
));
124176

125177
final URL url = this.resource("comments-test.yml");
126-
final YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
127-
.url(url).build();
178+
final YamlConfigurationLoader loader = YamlConfigurationLoader.builder().url(url).build();
128179

129180
final ConfigurationNode actual = loader.load();
130181
assertEquals(expected, actual);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# hello
2+
# what
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)