diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java index 3a3f17d4a..cb3fdf45c 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java @@ -1,8 +1,12 @@ package org.springframework.ide.vscode.commons.yaml.reconcile; +import static org.springframework.ide.vscode.commons.yaml.ast.NodeUtil.asScalar; + import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.util.ExceptionUtil; @@ -51,6 +55,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { switch (node.getNodeId()) { case mapping: MappingNode map = (MappingNode) node; + checkForDuplicateKeys(map); if (typeUtil.isMap(type)) { for (NodeTuple entry : map.getValue()) { reconcile(doc, entry.getKeyNode(), typeUtil.getKeyType(type)); @@ -108,6 +113,28 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } } + private void checkForDuplicateKeys(MappingNode node) { + Set duplicateKeys = new HashSet<>(); + Set seenKeys = new HashSet<>(); + for (NodeTuple entry : node.getValue()) { + String key = asScalar(entry.getKeyNode()); + if (key!=null) { + if (!seenKeys.add(key)) { + duplicateKeys.add(key); + } + } + } + if (!duplicateKeys.isEmpty()) { + for (NodeTuple entry : node.getValue()) { + Node keyNode = entry.getKeyNode(); + String key = asScalar(keyNode); + if (key!=null && duplicateKeys.contains(key)) { + problem(keyNode, "Duplicate key '"+key+"'"); + } + } + } + } + private void valueParseError(YType type, Node node, String parseErrorMsg) { if (!StringUtil.hasText(parseErrorMsg)) { parseErrorMsg= "Couldn't parse as '"+describe(type)+"'"; diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java index 6991e0730..c4a0de784 100644 --- a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java @@ -439,7 +439,30 @@ public class PipelineYamlEditorTest { , // => "repo-a<*>", "repo-b<*>" ); - + } + + @Test + public void reconcileDuplicateKeys() throws Exception { + Editor editor = harness.newEditor( + "resources:\n" + + "- name: my-repo\n" + + " type: git\n" + + " source:\n" + + " repository: https://github.com/kdvolder/my-repo\n" + + "resources:\n" + + "- name: your-repo\n" + + " type: git\n" + + " type: git\n" + + " source:\n" + + " repository: https://github.com/kdvolder/forked-repo\n" + ); + + editor.assertProblems( + "resources|Duplicate key", + "resources|Duplicate key", + "type|Duplicate key", + "type|Duplicate key" + ); } //////////////////////////////////////////////////////////////////////////////