json schema, improve type error highlighting

IDEA-185072 Kubernetes. Json. Add "type" tip into the inspection error description.
This commit is contained in:
Irina.Chernushina
2018-02-28 14:51:37 +01:00
parent 27714d7e69
commit 218d37dc7b
4 changed files with 55 additions and 53 deletions
@@ -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<JsonSchemaVariantsTreeBuilder.Step> steps =
skipProperties(JsonOriginalPsiWalker.INSTANCE.findPosition(object, false, true));
final List<JsonSchemaVariantsTreeBuilder.Step> position = JsonOriginalPsiWalker.INSTANCE.findPosition(object, false, true);
if (position == null) return;
final List<JsonSchemaVariantsTreeBuilder.Step> 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<JsonSchemaObject> 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<Object> 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<JsonSchemaObject> 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<JsonSchemaObject> schemas) {
final List<JsonSchemaType> list = new ArrayList<>();
for (JsonSchemaObject schema : schemas) {
final JsonSchemaType type = schema.getType();
if (type != null) {
list.add(type);
} else {
final List<JsonSchemaType> 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);
@@ -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\": [<warning descr=\"Less than a minimum 18\">16</warning>]}");
doTest(schema, "{\"prop\": [<warning descr=\"Type is not allowed\">\"test\"</warning>]}");
doTest(schema, "{\"prop\": [<warning descr=\"Type is not allowed. Expected: number.\">\"test\"</warning>]}");
}
public void testTopLevelArray() throws Exception {
@@ -113,7 +99,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
" \"type\": \"object\", \"properties\": {\"a\": {\"type\": \"number\"}}" +
" }\n" +
"}";
doTest(schema, "[{\"a\": <warning descr=\"Type is not allowed\">true</warning>}]");
doTest(schema, "[{\"a\": <warning descr=\"Type is not allowed. Expected: number.\">true</warning>}]");
doTest(schema, "[{\"a\": 18}]");
}
@@ -124,7 +110,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
" \"type\": \"number\", \"minimum\": 18" +
" }, {\"type\" : \"string\"}]\n" +
"}");
doTest(schema, "{\"prop\": [101, <warning descr=\"Type is not allowed\">102</warning>]}");
doTest(schema, "{\"prop\": [101, <warning descr=\"Type is not allowed. Expected: string.\">102</warning>]}");
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\": <warning descr=\"Type is not allowed\">\"unknown\"</warning>}," +
"\"office\": {\"street\": <warning descr=\"Type is not allowed\">5</warning>}}");
doTest(schema, "{\"home\": {\"street\": \"Broadway\", \"house\": <warning descr=\"Type is not allowed. Expected: integer.\">\"unknown\"</warning>}," +
"\"office\": {\"street\": <warning descr=\"Type is not allowed. Expected: string.\">5</warning>}}");
}
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\": <warning descr=\"Type is not allowed\">11</warning>}");
doTest(schema, "{\"a\" : 18, \"b\": \"wall\", \"c\": <warning descr=\"Type is not allowed. Expected: string.\">11</warning>}");
}
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\": <warning descr=\"Type is not allowed\">11</warning>}");
doTest(schema, "{\"prop\": <warning descr=\"Type is not allowed. Expected one of: boolean, string.\">11</warning>}");
}
@SuppressWarnings("Duplicates")
@@ -380,9 +366,9 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
"}";
doTest(schema, "{\n" +
" \"Abezjana\": 2,\n" +
" \"Auto\": <warning descr=\"Type is not allowed\">\"no\"</warning>,\n" +
" \"ABe\": <warning descr=\"Type is not allowed\">22</warning>,\n" +
" \"Boloto\": <warning descr=\"Type is not allowed\">2</warning>,\n" +
" \"Auto\": <warning descr=\"Type is not allowed. Expected: number.\">\"no\"</warning>,\n" +
" \"BAe\": <warning descr=\"Type is not allowed. Expected: boolean.\">22</warning>,\n" +
" \"Boloto\": <warning descr=\"Type is not allowed. Expected: boolean.\">2</warning>,\n" +
" \"Cyan\": <warning descr=\"Value should be one of: [\\\"test\\\", \\\"em\\\"]\">\"me\"</warning>\n" +
"}");
}
@@ -401,7 +387,7 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
" }\n" +
"}";
doTest(schema, "{\n" +
" \"p1\": <warning descr=\"Type is not allowed\">1</warning>,\n" +
" \"p1\": <warning descr=\"Type is not allowed. Expected: string.\">1</warning>,\n" +
" \"p2\": \"3\",\n" +
" \"a2\": \"auto!\",\n" +
" \"a1\": <warning descr=\"Value should be one of: [\\\"auto!\\\"]\">\"moto!\"</warning>\n" +
@@ -466,9 +452,9 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
"}";
doTest(schema, "{\n" +
" \"size\": <warning descr=\"Number of properties is greater than 3\">{\n" +
" \"a\": <warning descr=\"Type is not allowed\">1</warning>," +
" \"a\": <warning descr=\"Type is not allowed. Expected: boolean.\">1</warning>," +
" \"b\":3, \"c\": 4, " +
"\"a\": <warning descr=\"Type is not allowed\">5</warning>\n" +
"\"a\": <warning descr=\"Type is not allowed. Expected: boolean.\">5</warning>\n" +
" }</warning>\n" +
"}");
}
@@ -1,5 +1,5 @@
{
"definitions": {
"findMe": <warning descr="Type is not allowed">true</warning>
"findMe": <warning descr="Type is not allowed. Expected: integer.">true</warning>
}
}
@@ -2,5 +2,5 @@
"aaa": 1,
"bbb": true,
"s": 1,
"ccc": <warning descr="Type is not allowed">"3"</warning>
"ccc": <warning descr="Type is not allowed. Expected: integer.">"3"</warning>
}