diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java index 3af4da930..8a25552f3 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java @@ -148,4 +148,22 @@ public class NodeUtil { return new DocumentRegion(doc, tup.getKeyNode().getStartMark().getIndex(), tup.getValueNode().getEndMark().getIndex()); } + /** + * Detects whether a given map key-value pair is anchored. I.e. corresponds to + * a bit of yaml like this example: + * + *
+	 * some-key: &some-anchor
+	 *   blah: blah
+	 *   more: blah
+	 * 
+ */ + public static boolean isAnchored(NodeTuple entry) { + if (entry!=null) { + Node v = entry.getValueNode(); + return v!=null && v.getAnchor()!=null; + } + return false; + } + } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/YamlFileAST.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/YamlFileAST.java index ce637e116..58c4d76c6 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/YamlFileAST.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/YamlFileAST.java @@ -28,7 +28,6 @@ import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.TupleKeyRef; import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.TupleValueRef; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; -import org.yaml.snakeyaml.nodes.NodeTuple; import org.yaml.snakeyaml.nodes.SequenceNode; /** @@ -157,22 +156,4 @@ public class YamlFileAST { public IDocument getDocument() { return doc; } - - /** - * Detects whether a given map key-value pair is anchored. I.e. corresponds to - * a bit of yaml like this example: - * - *
-	 * some-key: &some-anchor
-	 *   blah: blah
-	 *   more: blah
-	 * 
- */ - public boolean isAnchored(NodeTuple entry) { - if (entry!=null) { - Node v = entry.getValueNode(); - return v!=null && v.getAnchor()!=null; - } - return false; - } } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java index 06be98b6e..99d7fdff3 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java @@ -171,7 +171,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } else { YTypedProperty prop = beanProperties.get(key); if (prop==null) { - if (!ast.isAnchored(entry)) { + if (!NodeUtil.isAnchored(entry)) { unknownBeanProperty(keyNode, type, key); } } else { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java index d25f1c6dc..c07cfc8ec 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java @@ -49,6 +49,7 @@ import org.springframework.ide.vscode.commons.util.ValueParseException; import org.springframework.ide.vscode.commons.util.ValueParser; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; import org.springframework.ide.vscode.commons.util.text.IDocument; +import org.springframework.ide.vscode.commons.yaml.ast.NodeMergeSupport; import org.springframework.ide.vscode.commons.yaml.ast.NodeRef; import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.Kind; import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.TupleValueRef; @@ -72,6 +73,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { private final IProblemCollector problems; private final TypeUtil typeUtil; private final IndexNavigator nav; + private final NodeMergeSupport nodeMerger; private AppYamlQuickfixes quickFixes; public ApplicationYamlASTReconciler(IProblemCollector problems, IndexNavigator nav, TypeUtil typeUtil, AppYamlQuickfixes quickFixes) { @@ -79,6 +81,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { this.typeUtil = typeUtil; this.nav = nav; this.quickFixes = quickFixes; + this.nodeMerger = new NodeMergeSupport(problems); } @Override @@ -98,8 +101,10 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { protected void reconcile(YamlFileAST root, Node node, IndexNavigator nav) { switch (node.getNodeId()) { case mapping: - checkForDuplicateKeys((MappingNode)node); - for (NodeTuple entry : ((MappingNode)node).getValue()) { + MappingNode map = (MappingNode) node; + nodeMerger.flattenMapping(map); + checkForDuplicateKeys(map); + for (NodeTuple entry : map.getValue()) { reconcile(root, entry, nav); } break; @@ -183,7 +188,9 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { } else { //both are null, this means there's no valid property with the current prefix //whether exact or extending it with further navigation - unkownProperty(root.getDocument().getUri(), keyNode, subNav.getPrefix(), entry, quickFixes.MISSING_PROPERTY); + if (!NodeUtil.isAnchored(entry)) { //See https://github.com/spring-projects/sts4/issues/420 + unkownProperty(root.getDocument().getUri(), keyNode, subNav.getPrefix(), entry, quickFixes.MISSING_PROPERTY); + } } } } @@ -221,6 +228,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { } private void reconcile(YamlFileAST root, MappingNode mapping, Type type) { + nodeMerger.flattenMapping(mapping); checkForDuplicateKeys(mapping); if (typeUtil.isAtomic(type)) { expectTypeFoundMapping(type, mapping); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java index cea9fe34a..3a00ab5e3 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java @@ -89,7 +89,33 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { //////////////////////////////////////////////////////////////////////////////////////// - @Test public void GH_404_ConsutructorBinding_support() throws Exception { + + @Test public void GH_420_anchorReference() throws Exception { + Editor editor; + data("config.bob", "java.lang.String", null, null); + data("config.dude", "java.lang.String", null, null); + + editor = newEditor( + "configref: &config\n" + + " bob: bob\n" + + " asdf: dude\n" + + "config:\n" + + " <<: *config" + ); + editor.assertProblems("asdf|Unknown"); + + editor = newEditor( + "configref: &config\n" + + " bob: bob\n" + + " asdf: dude\n" + + "config: *config" + ); + editor.assertProblems( + "asdf|Unknown" + ); + } + + @Test public void GH_404_ConstructorBinding_support() throws Exception { useProject(createPredefinedMavenProject("gh_404")); Editor editor;