diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java index 88cb88e7d029..ed9375c6d06b 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.jsonSchema.impl; import com.google.common.base.Predicates; @@ -117,8 +103,20 @@ class JsonSchemaAnnotatorChecker { myErrors.put(holder, error); } - private void typeError(final @NotNull PsiElement value) { - error("Type is not allowed", value); + private void typeError(final @NotNull PsiElement value, final @NotNull JsonSchemaType... allowedTypes) { + if (allowedTypes.length > 0) { + if (allowedTypes.length == 1) { + error(String.format("Type is not allowed. Expected: %s.", allowedTypes[0].getName()), value); + } else { + final String typesText = Arrays.stream(allowedTypes) + .map(JsonSchemaType::getName) + .sorted(Comparator.naturalOrder()) + .collect(Collectors.joining(", ")); + error(String.format("Type is not allowed. Expected one of: %s.", typesText), value); + } + } else { + error("Type is not allowed", value); + } myHadTypeError = true; } @@ -127,7 +125,7 @@ class JsonSchemaAnnotatorChecker { if (type != null) { JsonSchemaType schemaType = getMatchingSchemaType(schema, type); if (schemaType != null && !schemaType.equals(type)) { - typeError(value.getDelegate()); + typeError(value.getDelegate(), schemaType); } else if (JsonSchemaType._boolean.equals(type)) { checkForEnum(value.getDelegate(), schema); @@ -246,8 +244,9 @@ class JsonSchemaAnnotatorChecker { final JsonSchemaObject schemaObject = JsonSchemaService.Impl.get(object.getProject()).getSchemaObjectForSchemaFile(schemaFile); if (schemaObject == null) return; - final List steps = - skipProperties(JsonOriginalPsiWalker.INSTANCE.findPosition(object, false, true)); + final List position = JsonOriginalPsiWalker.INSTANCE.findPosition(object, false, true); + if (position == null) return; + final List steps = skipProperties(position); // !! not root schema, because we validate the schema written in the file itself final MatchResult result = new JsonSchemaResolver(schemaObject, false, steps).detailedResolve(); final List schemas = new ArrayList<>(result.mySchemas); @@ -301,10 +300,12 @@ class JsonSchemaAnnotatorChecker { private void checkForEnum(PsiElement value, JsonSchemaObject schema) { //enum values + pattern -> don't check enum values if (schema.getEnum() == null || schema.getPattern() != null) return; + final JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(value, schema); + if (walker == null) return; final String text = StringUtil.notNullize(value.getText()); final List objects = schema.getEnum(); for (Object object : objects) { - if (JsonLikePsiWalker.getWalker(value, schema).onlyDoubleQuotesForStringLiterals()) { + if (walker.onlyDoubleQuotesForStringLiterals()) { if (object.toString().equalsIgnoreCase(text)) return; } else { @@ -342,13 +343,13 @@ class JsonSchemaAnnotatorChecker { final JsonSchemaType type = JsonSchemaType.getType(value); JsonSchemaObject selected = null; if (type == null) { - if (!value.isShouldBeIgnored()) checker.typeError(value.getDelegate()); + if (!value.isShouldBeIgnored()) checker.typeError(value.getDelegate(), getExpectedTypes(collection)); } else { final List filtered = collection.stream() .filter(schema -> areSchemaTypesCompatible(schema, type)) .collect(Collectors.toList()); - if (filtered.isEmpty()) checker.typeError(value.getDelegate()); + if (filtered.isEmpty()) checker.typeError(value.getDelegate(), getExpectedTypes(collection)); else { if (isOneOf) { selected = checker.processOneOf(value, filtered); @@ -361,6 +362,21 @@ class JsonSchemaAnnotatorChecker { return Pair.create(selected, checker); } + private final static JsonSchemaType[] NO_TYPES = new JsonSchemaType[0]; + private static JsonSchemaType[] getExpectedTypes(final Collection schemas) { + final List list = new ArrayList<>(); + for (JsonSchemaObject schema : schemas) { + final JsonSchemaType type = schema.getType(); + if (type != null) { + list.add(type); + } else { + final List variants = schema.getTypeVariants(); + list.addAll(variants); + } + } + return list.isEmpty() ? NO_TYPES : list.toArray(new JsonSchemaType[0]); + } + public static boolean areSchemaTypesCompatible(@NotNull final JsonSchemaObject schema, @NotNull final JsonSchemaType type) { final JsonSchemaType matchingSchemaType = getMatchingSchemaType(schema, type); if (matchingSchemaType != null) return matchingSchemaType.equals(type); diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java index 1eaa55d36310..53642207de4a 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.jsonSchema; import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase; @@ -93,7 +79,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { "}"); doTest(schema, "{\"prop\": [101, 102]}"); doTest(schema, "{\"prop\": [16]}"); - doTest(schema, "{\"prop\": [\"test\"]}"); + doTest(schema, "{\"prop\": [\"test\"]}"); } public void testTopLevelArray() throws Exception { @@ -113,7 +99,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { " \"type\": \"object\", \"properties\": {\"a\": {\"type\": \"number\"}}" + " }\n" + "}"; - doTest(schema, "[{\"a\": true}]"); + doTest(schema, "[{\"a\": true}]"); doTest(schema, "[{\"a\": 18}]"); } @@ -124,7 +110,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { " \"type\": \"number\", \"minimum\": 18" + " }, {\"type\" : \"string\"}]\n" + "}"); - doTest(schema, "{\"prop\": [101, 102]}"); + doTest(schema, "{\"prop\": [101, 102]}"); doTest(schema, "{\"prop\": [101, \"102\"]}"); doTest(schema, "{\"prop\": [101, \"102\", \"additional\"]}"); @@ -181,8 +167,8 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { "\"office\": {\"$ref\": \"#/definitions/address\"}" + "}}"; doTest(schema, "{\"home\": {\"street\": \"Broadway\", \"house\": 11}}"); - doTest(schema, "{\"home\": {\"street\": \"Broadway\", \"house\": \"unknown\"}," + - "\"office\": {\"street\": 5}}"); + doTest(schema, "{\"home\": {\"street\": \"Broadway\", \"house\": \"unknown\"}," + + "\"office\": {\"street\": 5}}"); } public void testAdditionalPropertiesAllowed() throws Exception { @@ -199,7 +185,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { public void testAdditionalPropertiesSchema() throws Exception { final String schema = "{\"type\": \"object\", \"properties\": {\"a\": {}}," + "\"additionalProperties\": {\"type\": \"string\"}}"; - doTest(schema, "{\"a\" : 18, \"b\": \"wall\", \"c\": 11}"); + doTest(schema, "{\"a\" : 18, \"b\": \"wall\", \"c\": 11}"); } public void testMinMaxProperties() throws Exception { @@ -217,7 +203,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { final String schema = schema("{\"oneOf\": [" + StringUtil.join(subSchemas, ", ") + "]}"); doTest(schema, "{\"prop\": \"abc\"}"); doTest(schema, "{\"prop\": true}"); - doTest(schema, "{\"prop\": 11}"); + doTest(schema, "{\"prop\": 11}"); } @SuppressWarnings("Duplicates") @@ -380,9 +366,9 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { "}"; doTest(schema, "{\n" + " \"Abezjana\": 2,\n" + - " \"Auto\": \"no\",\n" + - " \"ABe\": 22,\n" + - " \"Boloto\": 2,\n" + + " \"Auto\": \"no\",\n" + + " \"BAe\": 22,\n" + + " \"Boloto\": 2,\n" + " \"Cyan\": \"me\"\n" + "}"); } @@ -401,7 +387,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { " }\n" + "}"; doTest(schema, "{\n" + - " \"p1\": 1,\n" + + " \"p1\": 1,\n" + " \"p2\": \"3\",\n" + " \"a2\": \"auto!\",\n" + " \"a1\": \"moto!\"\n" + @@ -466,9 +452,9 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { "}"; doTest(schema, "{\n" + " \"size\": {\n" + - " \"a\": 1," + + " \"a\": 1," + " \"b\":3, \"c\": 4, " + - "\"a\": 5\n" + + "\"a\": 5\n" + " }\n" + "}"); } diff --git a/json/tests/testData/jsonSchema/crossReferences/nestedAllOneAnyWithInheritance/testHighlighting.json b/json/tests/testData/jsonSchema/crossReferences/nestedAllOneAnyWithInheritance/testHighlighting.json index 12f8ed830ca0..f59c069486d3 100644 --- a/json/tests/testData/jsonSchema/crossReferences/nestedAllOneAnyWithInheritance/testHighlighting.json +++ b/json/tests/testData/jsonSchema/crossReferences/nestedAllOneAnyWithInheritance/testHighlighting.json @@ -1,5 +1,5 @@ { "definitions": { - "findMe": true + "findMe": true } } \ No newline at end of file diff --git a/json/tests/testData/jsonSchema/highlighting/testCycledSchema.json b/json/tests/testData/jsonSchema/highlighting/testCycledSchema.json index eb663c85e451..685cb46b7910 100644 --- a/json/tests/testData/jsonSchema/highlighting/testCycledSchema.json +++ b/json/tests/testData/jsonSchema/highlighting/testCycledSchema.json @@ -2,5 +2,5 @@ "aaa": 1, "bbb": true, "s": 1, - "ccc": "3" + "ccc": "3" } \ No newline at end of file