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 585c76837..e33b974d9 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 @@ -235,6 +235,9 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { if (typeUtil.isAtomic(type)) { expectTypeFoundMapping(type, mapping); } else if (typeUtil.isMap(type) || typeUtil.isSequencable(type)) { + if (typeUtil.isMap(type)) { + checkForEscapableKeys(mapping); + } Type keyType = typeUtil.getKeyType(type); Type valueType = TypeUtil.getDomainType(type); if (keyType!=null) { @@ -287,6 +290,36 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { } } + private void checkForEscapableKeys(MappingNode mapping) { + if (mapping!=null) { + for (NodeTuple tup : mapping.getValue()) { + Node keyNode = tup.getKeyNode(); + String key = NodeUtil.asScalar(keyNode); + if (needsEscaping(key)) { + problems.accept(problem(ApplicationYamlProblemType.YAML_SHOULD_ESCAPE, keyNode, + "This key is used in a map and contains special characters. It is recommended to escape it by surrounding it with '[]'")); + } + } + } + } + + private boolean needsEscaping(String key) { + if (key!=null) { + if (key.startsWith("[")) { + return false; // looks like its already escaped. So doesn't need more escaping. + } + for (int i = 0; i < key.length(); i++) { + char c = key.charAt(i); + if (c=='-'||Character.isAlphabetic(c)||Character.isDigit(c)) { + //fine! + } else { + return true; + } + } + } + return false; + } + private void reconcile(YamlFileAST root, SequenceNode seq, Type type) { if (typeUtil.isAtomic(type)) { expectTypeFoundSequence(type, seq); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlProblemType.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlProblemType.java index 0e8d3619c..45f903ae0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlProblemType.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlProblemType.java @@ -32,7 +32,8 @@ public enum ApplicationYamlProblemType implements ProblemType { YAML_INVALID_BEAN_PROPERTY("Accessing a named property in a type that doesn't provide a property accessor with that name"), YAML_DEPRECATED_ERROR(ERROR, "Property is marked as Deprecated(Error)"), YAML_DEPRECATED_WARNING(WARNING, "Property is marked as Deprecated(Warning)"), - YAML_DUPLICATE_KEY("A mapping node contains multiple entries for the same key"); + YAML_DUPLICATE_KEY("A mapping node contains multiple entries for the same key"), + YAML_SHOULD_ESCAPE(WARNING, "This key contains special characters and should probably be escaped by surrounding it with '[]'"); private final ProblemSeverity defaultSeverity; private String description; 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 c40512b25..2b8168119 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 @@ -130,6 +130,26 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { ); } + @Test public void test_GH_534_warning() throws Exception { + //See: https://www.pivotaltracker.com/n/projects/1346850/stories/174872660 + IJavaProject p = createPredefinedMavenProject("map-of-pojo"); + useProject(p); + + //detect values in map keys that are recommended to be escpaped. + Editor editor = newEditor( + "my:\n" + + " map:\n" + + " foo.bar:\n" + + " name: jeff\n" + + " '[rab.dab]':\n" + //no warn for this, already escaped! + " name: jeff\n" + + " yaza:\n" + //nothing special, so no warning either. + " name: good" + ); + Diagnostic problem = editor.assertProblems("foo.bar|escape it by surrounding it with '[]'").get(0); + assertEquals(DiagnosticSeverity.Warning, problem.getSeverity()); + } + @Test public void handleAsKey() throws Exception { //See: https://www.pivotaltracker.com/story/show/174954118 useProject(createPredefinedMavenProject("justauth-example")); diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/map-of-pojo/src/main/resources/application.yml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/map-of-pojo/src/main/resources/application.yml new file mode 100644 index 000000000..83afce13b --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/map-of-pojo/src/main/resources/application.yml @@ -0,0 +1,4 @@ +my: + map: + foo.bar: + name: jeff \ No newline at end of file