From 5ac6d4010e007410a2a19e2efcdd64ad12cdcf0f Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Tue, 20 Feb 2018 13:28:06 -0800 Subject: [PATCH] Proper support for Set types in application.yml editor --- .../ApplicationYamlASTReconciler.java | 54 +++++++----- .../boot/test/ApplicationYamlEditorTest.java | 82 +++++++++++++++---- 2 files changed, 103 insertions(+), 33 deletions(-) diff --git a/headless-services/commons/commons-boot/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java b/headless-services/commons/commons-boot/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java index b45ba33ea..852a8f85d 100644 --- a/headless-services/commons/commons-boot/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java +++ b/headless-services/commons/commons-boot/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java @@ -31,8 +31,12 @@ import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyN import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode; import org.springframework.ide.vscode.boot.metadata.types.TypedProperty; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; +import org.springframework.ide.vscode.commons.util.ExceptionUtil; import org.springframework.ide.vscode.commons.util.StringUtil; +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.NodeRef; import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.Kind; import org.springframework.ide.vscode.commons.yaml.ast.NodeRef.TupleValueRef; @@ -66,21 +70,21 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { reconcile(ast, nav); } - protected void reconcile(YamlFileAST ast, IndexNavigator nav) { - List nodes = ast.getNodes(); + protected void reconcile(YamlFileAST root, IndexNavigator nav) { + List nodes = root.getNodes(); if (nodes!=null && !nodes.isEmpty()) { for (Node node : nodes) { - reconcile(node, nav); + reconcile(root, node, nav); } } } - protected void reconcile(Node node, IndexNavigator nav) { + protected void reconcile(YamlFileAST root, Node node, IndexNavigator nav) { switch (node.getNodeId()) { case mapping: checkForDuplicateKeys((MappingNode)node); for (NodeTuple entry : ((MappingNode)node).getValue()) { - reconcile(entry, nav); + reconcile(root, entry, nav); } break; case scalar: @@ -121,7 +125,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { return propName!=null && propName.equals("spring.profiles"); } - private void reconcile(NodeTuple entry, IndexNavigator nav) { + private void reconcile(YamlFileAST root, NodeTuple entry, IndexNavigator nav) { Node keyNode = entry.getKeyNode(); String key = asScalar(keyNode); if (key==null) { @@ -153,12 +157,12 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { if (match.isDeprecated()) { deprecatedProperty(match, keyNode); } - reconcile(entry.getValueNode(), type); + reconcile(root, entry.getValueNode(), type); } else if (extension!=null) { //We don't really care about the extension only about the fact that it // exists and so it is meaningful to continue checking... Node valueNode = entry.getValueNode(); - reconcile(valueNode, subNav); + reconcile(root, valueNode, subNav); } else { //both are null, this means there's no valid property with the current prefix //whether exact or extending it with further navigation @@ -170,17 +174,17 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { /** * Reconcile a node given the type that we expect the node to be. */ - private void reconcile(Node node, Type type) { + private void reconcile(YamlFileAST root, Node node, Type type) { if (type!=null) { switch (node.getNodeId()) { case scalar: - reconcile((ScalarNode)node, type); + reconcile(root, (ScalarNode)node, type); break; case sequence: - reconcile((SequenceNode)node, type); + reconcile(root, (SequenceNode)node, type); break; case mapping: - reconcile((MappingNode)node, type); + reconcile(root, (MappingNode)node, type); break; case anchor: //TODO: what should we do with anchor nodes @@ -191,7 +195,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { } } - private void reconcile(MappingNode mapping, Type type) { + private void reconcile(YamlFileAST root, MappingNode mapping, Type type) { checkForDuplicateKeys(mapping); if (typeUtil.isAtomic(type)) { expectTypeFoundMapping(type, mapping); @@ -200,7 +204,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { Type valueType = TypeUtil.getDomainType(type); if (keyType!=null) { for (NodeTuple entry : mapping.getValue()) { - reconcile(entry.getKeyNode(), keyType); + reconcile(root, entry.getKeyNode(), keyType); } } if (valueType!=null) { @@ -217,7 +221,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { nestedValueType = type; } } - reconcile(entry.getValueNode(), nestedValueType); + reconcile(root, entry.getValueNode(), nestedValueType); } } } else { @@ -239,7 +243,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { if (typedProperty.isDeprecated()) { deprecatedProperty(type, typedProperty, keyNode); } - reconcile(valNode, typedProperty.getType()); + reconcile(root, valNode, typedProperty.getType()); } } } @@ -248,14 +252,14 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { } } - private void reconcile(SequenceNode seq, Type type) { + private void reconcile(YamlFileAST root, SequenceNode seq, Type type) { if (typeUtil.isAtomic(type)) { expectTypeFoundSequence(type, seq); } else if (TypeUtil.isSequencable(type)) { Type domainType = TypeUtil.getDomainType(type); if (domainType!=null) { for (Node element : seq.getValue()) { - reconcile(element, domainType); + reconcile(root, element, domainType); } } } else { @@ -264,7 +268,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { } - private void reconcile(ScalarNode scalar, Type type) { + private void reconcile(YamlFileAST root, ScalarNode scalar, Type type) { String stringValue = scalar.getValue(); if (!hasPlaceHolder(stringValue)) { //don't check anything with placeholder expressions in it ValueParser valueParser = typeUtil.getValueParser(type); @@ -274,6 +278,8 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { // expected type try { valueParser.parse(stringValue); + } catch (ValueParseException e) { + valueParseError(root, scalar, e); } catch (Exception e) { //Couldn't parse valueTypeMismatch(type, scalar); @@ -300,6 +306,12 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { expectType(ApplicationYamlProblemType.YAML_VALUE_TYPE_MISMATCH, type, scalar); } + private void valueParseError(YamlFileAST root, ScalarNode scalar, ValueParseException e) { + IDocument doc = root.getDocument(); + DocumentRegion containingRegion = new DocumentRegion(doc, scalar.getStartMark().getIndex(), scalar.getEndMark().getIndex()); + problems.accept(problem(ApplicationYamlProblemType.YAML_VALUE_TYPE_MISMATCH, e.getHighlightRegion(containingRegion), ExceptionUtil.getMessage(e))); + } + private void unkownProperty(Node node, String name, NodeTuple entry) { SpringPropertyProblem p = problem(ApplicationYamlProblemType.YAML_UNKNOWN_PROPERTY, node, "Unknown property '"+name+"'"); p.setPropertyName(extendForQuickfix(StringUtil.camelCaseToHyphens(name), entry.getValueNode())); @@ -378,6 +390,10 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { return SpringPropertyProblem.problem(type, msg, start, end-start); } + private SpringPropertyProblem problem(ApplicationYamlProblemType type, DocumentRegion region, String msg) { + return SpringPropertyProblem.problem(type, msg, region.getStart(), region.getLength()); + } + private String describe(Node node) { switch (node.getNodeId()) { case scalar: diff --git a/headless-services/commons/commons-boot/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java b/headless-services/commons/commons-boot/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java index 8b4d85ff4..728a536a0 100644 --- a/headless-services/commons/commons-boot/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java +++ b/headless-services/commons/commons-boot/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java @@ -17,6 +17,7 @@ import static org.springframework.ide.vscode.languageserver.testharness.Editor.I import java.time.Duration; +import org.eclipse.core.resources.IProject; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.Diagnostic; import org.junit.Ignore; @@ -29,11 +30,11 @@ import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider; import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog; import org.springframework.ide.vscode.boot.metadata.CachingValueProvider; import org.springframework.ide.vscode.boot.metadata.PropertyInfo; -import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.composable.ComposableLanguageServer; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.languageserver.testharness.Editor; @@ -715,19 +716,9 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { ); editor.assertProblems( - "bad: BLUE|Expecting a 'demo.Color", - "Bogus|Expecting a 'demo.Color" + "bad: BLUE|demo.Color", + "Bogus|demo.Color" ); - - /* - * TODO: if enums are not sorted by the 3rd party java indexing lib then - * perform the commented out test rather than the above - */ - // editor.assertProblems( -// "bad: BLUE|Expecting a 'demo.Color[RED, GREEN, BLUE]' but got a 'Mapping' node", -// "Bogus|Expecting a 'demo.Color[RED, GREEN, BLUE]' but got 'Bogus'" -// ); - } @Test public void testReconcileSkipIfNoMetadata() throws Exception { @@ -3459,7 +3450,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { " - GREEN\n" ); editor.assertProblems( - "not-a-color|Expecting a 'com.wellsfargo.lendingplatform.web.config.Color" + "not-a-color|com.wellsfargo.lendingplatform.web.config.Color" ); editor = newEditor( @@ -3690,6 +3681,69 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { editor.assertProblems("bogus|Unknown property"); } + @Test public void testSetOfEnumReconcile() throws Exception { + String collectionType = "java.util.Set"; + doCollectionOfEnumReconcileTest(collectionType); + } + + @Test public void testListOfEnumReconcile() throws Exception { + String collectionType = "java.util.List"; + doCollectionOfEnumReconcileTest(collectionType); + } + + private void doCollectionOfEnumReconcileTest(String collectionType) throws Exception { + useProject(createPredefinedMavenProject("enums-boot-1.3.2-app")); + data("my.colors", collectionType + "", null, "Ooh! nice colors!"); + Editor editor; + + //comma-separated string + editor = newEditor( + "my:\n" + + " colors: red,green,BLUE,not-a-color\n" + ); + editor.assertProblems("not-a-color|demo.Color"); + + //comma-separated string + editor = newEditor( + "my:\n" + + " colors: red, green, not-a-color, BLUE\n" + ); + editor.assertProblems("not-a-color|demo.Color"); + + //flow list + editor = newEditor( + "my:\n" + + " colors: [red, green, BLUE, not-a-color]" + ); + editor.assertProblems("not-a-color|demo.Color"); + + //block list + editor = newEditor( + "my:\n" + + " colors:\n" + + " - red\n" + + " - green\n" + + " - BLUE\n" + + " - not-a-color\n" + ); + editor.assertProblems("not-a-color|demo.Color"); + } + + @Test public void testSetOfEnumCompletions() throws Exception { + useProject(createPredefinedMavenProject("enums-boot-1.3.2-app")); + data("my.colors", "java.util.Set", null, "Ooh! nice colors!"); + + Editor editor = newEditor( + "my:\n" + + " colors:\n" + + " - <*>" + ); + editor.assertContextualCompletions("<*>", "blue<*>", "green<*>", "red<*>"); + editor.assertContextualCompletions("b<*>", "blue<*>"); + editor.assertContextualCompletions("B<*>", "BLUE<*>"); + } + + ///////////////// cruft //////////////////////////////////////////////////////// private void generateNestedProperties(int levels, String[] names, String prefix) {