From 782d513f66308eeaf4cae43bdea83ae3e605c004 Mon Sep 17 00:00:00 2001 From: Anton Lobov Date: Thu, 5 Jul 2018 17:02:17 +0200 Subject: [PATCH] WEB-33724, WEB-33306 - json: better handle anyOf and oneOf select the least erroneous schema to show errors when none of candidates has match least erroneous option should at most correspond to what the user wanted to do --- .../impl/JsonSchemaAnnotatorChecker.java | 200 ++++++++++++------ .../jsonSchema/impl/JsonValidationError.java | 15 +- .../JsonSchemaHighlightingTest.java | 24 +++ .../highlighting/anyOfBestChoiceSchema.json | 76 +++++++ .../highlighting/oneOfBestChoiceSchema.json | 36 ++++ 5 files changed, 280 insertions(+), 71 deletions(-) create mode 100644 json/tests/testData/jsonSchema/highlighting/anyOfBestChoiceSchema.json create mode 100644 json/tests/testData/jsonSchema/highlighting/oneOfBestChoiceSchema.json diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java index 2f1d71e63155..1da1e687bd9a 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java @@ -21,6 +21,7 @@ import com.jetbrains.jsonSchema.extension.adapters.JsonObjectValueAdapter; import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter; import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter; import com.jetbrains.jsonSchema.ide.JsonSchemaService; +import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -101,17 +102,21 @@ class JsonSchemaAnnotatorChecker { return checker; } - private void error(final String error, final PsiElement holder) { - error(error, holder, JsonValidationError.FixableIssueKind.None, null); + private void error(final String error, final PsiElement holder, + @MagicConstant(flagsFromClass = JsonValidationError.class) int priority) { + error(error, holder, JsonValidationError.FixableIssueKind.None, null, priority); } private void error(final PsiElement newHolder, JsonValidationError error) { - error(error.getMessage(), newHolder, error.getFixableIssueKind(), error.getIssueData()); + error(error.getMessage(), newHolder, error.getFixableIssueKind(), error.getIssueData(), error.getPriority()); } - private void error(final String error, final PsiElement holder, JsonValidationError.FixableIssueKind fixableIssueKind, JsonValidationError.IssueData data) { + private void error(final String error, final PsiElement holder, + JsonValidationError.FixableIssueKind fixableIssueKind, + JsonValidationError.IssueData data, + @MagicConstant(flagsFromClass = JsonValidationError.class) int priority) { if (myErrors.containsKey(holder)) return; - myErrors.put(holder, new JsonValidationError(error, fixableIssueKind, data)); + myErrors.put(holder, new JsonValidationError(error, fixableIssueKind, data, priority)); } private void typeError(final @NotNull PsiElement value, final @NotNull JsonSchemaType... allowedTypes) { @@ -119,7 +124,8 @@ class JsonSchemaAnnotatorChecker { if (allowedTypes.length == 1) { error(String.format("Type is not allowed. Expected: %s.", allowedTypes[0].getName()), value, JsonValidationError.FixableIssueKind.ProhibitedType, - new JsonValidationError.TypeMismatchIssueData(allowedTypes)); + new JsonValidationError.TypeMismatchIssueData(allowedTypes), + JsonValidationError.HIGH_PRIORITY); } else { final String typesText = Arrays.stream(allowedTypes) .map(JsonSchemaType::getName) @@ -128,10 +134,11 @@ class JsonSchemaAnnotatorChecker { .collect(Collectors.joining(", ")); error(String.format("Type is not allowed. Expected one of: %s.", typesText), value, JsonValidationError.FixableIssueKind.ProhibitedType, - new JsonValidationError.TypeMismatchIssueData(allowedTypes)); + new JsonValidationError.TypeMismatchIssueData(allowedTypes), + JsonValidationError.HIGH_PRIORITY); } } else { - error("Type is not allowed", value); + error("Type is not allowed", value, JsonValidationError.HIGH_PRIORITY); } myHadTypeError = true; } @@ -206,7 +213,7 @@ class JsonSchemaAnnotatorChecker { .anyMatch(s -> schema.getJsonObject().equals(s.getJsonObject()))) return; final JsonSchemaAnnotatorChecker checker = checkByMatchResult(value, result); - if (checker == null || checker.isCorrect()) error("Validates against 'not' schema", value.getDelegate()); + if (checker == null || checker.isCorrect()) error("Validates against 'not' schema", value.getDelegate(), JsonValidationError.MEDIUM_PRIORITY); } if (schema.getIf() != null) { @@ -218,7 +225,7 @@ class JsonSchemaAnnotatorChecker { if (checker.isCorrect()) { JsonSchemaObject then = schema.getThen(); if (then == null) { - error("Validates against 'if' branch but no 'then' branch is present", value.getDelegate()); + error("Validates against 'if' branch but no 'then' branch is present", value.getDelegate(), JsonValidationError.LOW_PRIORITY); } else { checkObjectBySchemaRecordErrors(then, value); @@ -227,7 +234,7 @@ class JsonSchemaAnnotatorChecker { else { JsonSchemaObject schemaElse = schema.getElse(); if (schemaElse == null) { - error("Validates counter 'if' branch but no 'else' branch is present", value.getDelegate()); + error("Validates counter 'if' branch but no 'else' branch is present", value.getDelegate(), JsonValidationError.LOW_PRIORITY); } else { checkObjectBySchemaRecordErrors(schemaElse, value); @@ -277,7 +284,7 @@ class JsonSchemaAnnotatorChecker { if (ThreeState.NO.equals(pair.getFirst()) && !set.contains(name)) { error(JsonBundle.message("json.schema.annotation.not.allowed.property", name), property.getDelegate(), JsonValidationError.FixableIssueKind.ProhibitedProperty, - new JsonValidationError.ProhibitedPropertyIssueData(name)); + new JsonValidationError.ProhibitedPropertyIssueData(name), JsonValidationError.LOW_PRIORITY); } else if (ThreeState.UNSURE.equals(pair.getFirst()) && property.getValue() != null) { checkObjectBySchemaRecordErrors(pair.getSecond(), property.getValue()); @@ -292,14 +299,15 @@ class JsonSchemaAnnotatorChecker { requiredNames.removeAll(set); if (!requiredNames.isEmpty()) { JsonValidationError.MissingMultiplePropsIssueData data = createMissingPropertiesData(schema, requiredNames); - error("Missing required " + data.getMessage(false), value.getDelegate(), JsonValidationError.FixableIssueKind.MissingProperty, data); + error("Missing required " + data.getMessage(false), value.getDelegate(), JsonValidationError.FixableIssueKind.MissingProperty, data, + JsonValidationError.HIGH_PRIORITY); } } if (schema.getMinProperties() != null && propertyList.size() < schema.getMinProperties()) { - error("Number of properties is less than " + schema.getMinProperties(), value.getDelegate()); + error("Number of properties is less than " + schema.getMinProperties(), value.getDelegate(), JsonValidationError.LOW_PRIORITY); } if (schema.getMaxProperties() != null && propertyList.size() > schema.getMaxProperties()) { - error("Number of properties is greater than " + schema.getMaxProperties(), value.getDelegate()); + error("Number of properties is greater than " + schema.getMaxProperties(), value.getDelegate(), JsonValidationError.LOW_PRIORITY); } final Map> dependencies = schema.getPropertyDependencies(); if (dependencies != null) { @@ -313,7 +321,7 @@ class JsonSchemaAnnotatorChecker { error("Dependency is violated: " + data.getMessage(false) + " must be specified, since '" + entry.getKey() + "' is specified", value.getDelegate(), JsonValidationError.FixableIssueKind.MissingProperty, - data); + data, JsonValidationError.HIGH_PRIORITY); } } } @@ -409,7 +417,7 @@ class JsonSchemaAnnotatorChecker { final JsonProperty pattern = ((JsonObject)element).findProperty("pattern"); if (pattern != null) { - error(StringUtil.convertLineSeparators(patternError), pattern.getValue()); + error(StringUtil.convertLineSeparators(patternError), pattern.getValue(), JsonValidationError.LOW_PRIORITY); } } } @@ -423,7 +431,7 @@ class JsonSchemaAnnotatorChecker { if (element == null || !element.isValid()) continue; final PsiElement parent = element.getParent(); if (parent instanceof JsonProperty) { - error(StringUtil.convertLineSeparators(entry.getValue()), ((JsonProperty)parent).getNameElement()); + error(StringUtil.convertLineSeparators(entry.getValue()), ((JsonProperty)parent).getNameElement(), JsonValidationError.LOW_PRIORITY); } } } @@ -460,7 +468,7 @@ class JsonSchemaAnnotatorChecker { } } error("Value should be one of: [" + StringUtil.join(objects, o -> o.toString(), ", ") + "]", value, - JsonValidationError.FixableIssueKind.NonEnumValue, null); + JsonValidationError.FixableIssueKind.NonEnumValue, null, JsonValidationError.MEDIUM_PRIORITY); } private static boolean equalsIgnoreQuotesAndCase(@NotNull final String s1, @NotNull final String s2, boolean requireQuotedValues) { @@ -476,7 +484,7 @@ class JsonSchemaAnnotatorChecker { if (asArray == null) return; final List elements = asArray.getElements(); if (schema.getMinLength() != null && elements.size() < schema.getMinLength()) { - error("Array is shorter than " + schema.getMinLength(), value.getDelegate()); + error("Array is shorter than " + schema.getMinLength(), value.getDelegate(), JsonValidationError.LOW_PRIORITY); return; } checkArrayItems(value, elements, schema); @@ -594,7 +602,7 @@ class JsonSchemaAnnotatorChecker { for (Map.Entry> entry: valueTexts.entrySet()) { if (entry.getValue().size() > 1) { for (JsonValueAdapter item: entry.getValue()) { - error("Item is not unique", item.getDelegate()); + error("Item is not unique", item.getDelegate(), JsonValidationError.HIGH_PRIORITY); } } } @@ -609,7 +617,7 @@ class JsonSchemaAnnotatorChecker { } } if (!match) { - error("No match for 'contains' rule", array.getDelegate()); + error("No match for 'contains' rule", array.getDelegate(), JsonValidationError.MEDIUM_PRIORITY); } } if (schema.getItemsSchema() != null) { @@ -625,7 +633,7 @@ class JsonSchemaAnnotatorChecker { } else { if (!Boolean.TRUE.equals(schema.getAdditionalItemsAllowed())) { - error("Additional items are not allowed", arrayValue.getDelegate()); + error("Additional items are not allowed", arrayValue.getDelegate(), JsonValidationError.LOW_PRIORITY); } else if (schema.getAdditionalItemsSchema() != null) { checkObjectBySchemaRecordErrors(schema.getAdditionalItemsSchema(), arrayValue); @@ -634,10 +642,10 @@ class JsonSchemaAnnotatorChecker { } } if (schema.getMinItems() != null && list.size() < schema.getMinItems()) { - error("Array is shorter than " + schema.getMinItems(), array.getDelegate()); + error("Array is shorter than " + schema.getMinItems(), array.getDelegate(), JsonValidationError.LOW_PRIORITY); } if (schema.getMaxItems() != null && list.size() > schema.getMaxItems()) { - error("Array is longer than " + schema.getMaxItems(), array.getDelegate()); + error("Array is longer than " + schema.getMaxItems(), array.getDelegate(), JsonValidationError.LOW_PRIORITY); } } @@ -655,23 +663,23 @@ class JsonSchemaAnnotatorChecker { final String value = StringUtil.unquoteString(walker.getNodeTextForValidation(propValue)); if (schema.getMinLength() != null) { if (value.length() < schema.getMinLength()) { - error("String is shorter than " + schema.getMinLength(), propValue); + error("String is shorter than " + schema.getMinLength(), propValue, JsonValidationError.LOW_PRIORITY); return; } } if (schema.getMaxLength() != null) { if (value.length() > schema.getMaxLength()) { - error("String is longer than " + schema.getMaxLength(), propValue); + error("String is longer than " + schema.getMaxLength(), propValue, JsonValidationError.LOW_PRIORITY); return; } } if (schema.getPattern() != null) { if (schema.getPatternError() != null) { error("Can not check string by pattern because of error: " + StringUtil.convertLineSeparators(schema.getPatternError()), - propValue); + propValue, JsonValidationError.LOW_PRIORITY); } if (!schema.checkByPattern(value)) { - error("String is violating the pattern: '" + StringUtil.convertLineSeparators(schema.getPattern()) + "'", propValue); + error("String is violating the pattern: '" + StringUtil.convertLineSeparators(schema.getPattern()) + "'", propValue, JsonValidationError.LOW_PRIORITY); } } // I think we are not gonna to support format, there are a couple of RFCs there to check upon.. @@ -701,7 +709,7 @@ class JsonSchemaAnnotatorChecker { catch (NumberFormatException e) { error("Integer value expected", propValue, JsonValidationError.FixableIssueKind.TypeMismatch, - new JsonValidationError.TypeMismatchIssueData(new JsonSchemaType[]{schemaType})); + new JsonValidationError.TypeMismatchIssueData(new JsonSchemaType[]{schemaType}), JsonValidationError.HIGH_PRIORITY); return; } } @@ -713,7 +721,7 @@ class JsonSchemaAnnotatorChecker { if (!JsonSchemaType._string_number.equals(schemaType)) { error("Double value expected", propValue, JsonValidationError.FixableIssueKind.TypeMismatch, - new JsonValidationError.TypeMismatchIssueData(new JsonSchemaType[]{schemaType})); + new JsonValidationError.TypeMismatchIssueData(new JsonSchemaType[]{schemaType}), JsonValidationError.HIGH_PRIORITY); } return; } @@ -724,7 +732,7 @@ class JsonSchemaAnnotatorChecker { if (leftOver > 0.000001) { final String multipleOfValue = String.valueOf(Math.abs(multipleOf.doubleValue() - multipleOf.intValue()) < 0.000001 ? multipleOf.intValue() : multipleOf); - error("Is not multiple of " + multipleOfValue, propValue); + error("Is not multiple of " + multipleOfValue, propValue, JsonValidationError.LOW_PRIORITY); return; } } @@ -741,13 +749,13 @@ class JsonSchemaAnnotatorChecker { if (JsonSchemaType._integer.equals(propValueType)) { final int intValue = exclusiveMaximumNumber.intValue(); if (value.intValue() >= intValue) { - error("Greater than an exclusive maximum " + intValue, propertyValue); + error("Greater than an exclusive maximum " + intValue, propertyValue, JsonValidationError.LOW_PRIORITY); } } else { final double doubleValue = exclusiveMaximumNumber.doubleValue(); if (value.doubleValue() >= doubleValue) { - error("Greater than an exclusive maximum " + exclusiveMaximumNumber, propertyValue); + error("Greater than an exclusive maximum " + exclusiveMaximumNumber, propertyValue, JsonValidationError.LOW_PRIORITY); } } } @@ -758,12 +766,12 @@ class JsonSchemaAnnotatorChecker { final int intValue = maximum.intValue(); if (isExclusive) { if (value.intValue() >= intValue) { - error("Greater than an exclusive maximum " + intValue, propertyValue); + error("Greater than an exclusive maximum " + intValue, propertyValue, JsonValidationError.LOW_PRIORITY); } } else { if (value.intValue() > intValue) { - error("Greater than a maximum " + intValue, propertyValue); + error("Greater than a maximum " + intValue, propertyValue, JsonValidationError.LOW_PRIORITY); } } } @@ -771,12 +779,12 @@ class JsonSchemaAnnotatorChecker { final double doubleValue = maximum.doubleValue(); if (isExclusive) { if (value.doubleValue() >= doubleValue) { - error("Greater than an exclusive maximum " + maximum, propertyValue); + error("Greater than an exclusive maximum " + maximum, propertyValue, JsonValidationError.LOW_PRIORITY); } } else { if (value.doubleValue() > doubleValue) { - error("Greater than a maximum " + maximum, propertyValue); + error("Greater than a maximum " + maximum, propertyValue, JsonValidationError.LOW_PRIORITY); } } } @@ -790,13 +798,13 @@ class JsonSchemaAnnotatorChecker { if (JsonSchemaType._integer.equals(schemaType)) { final int intValue = exclusiveMinimumNumber.intValue(); if (value.intValue() <= intValue) { - error("Less than an exclusive minimum" + intValue, propertyValue); + error("Less than an exclusive minimum" + intValue, propertyValue, JsonValidationError.LOW_PRIORITY); } } else { final double doubleValue = exclusiveMinimumNumber.doubleValue(); if (value.doubleValue() <= doubleValue) { - error("Less than an exclusive minimum " + exclusiveMinimumNumber, propertyValue); + error("Less than an exclusive minimum " + exclusiveMinimumNumber, propertyValue, JsonValidationError.LOW_PRIORITY); } } } @@ -808,12 +816,12 @@ class JsonSchemaAnnotatorChecker { final int intValue = minimum.intValue(); if (isExclusive) { if (value.intValue() <= intValue) { - error("Less than an exclusive minimum " + intValue, propertyValue); + error("Less than an exclusive minimum " + intValue, propertyValue, JsonValidationError.LOW_PRIORITY); } } else { if (value.intValue() < intValue) { - error("Less than a minimum " + intValue, propertyValue); + error("Less than a minimum " + intValue, propertyValue, JsonValidationError.LOW_PRIORITY); } } } @@ -821,12 +829,12 @@ class JsonSchemaAnnotatorChecker { final double doubleValue = minimum.doubleValue(); if (isExclusive) { if (value.doubleValue() <= doubleValue) { - error("Less than an exclusive minimum " + minimum, propertyValue); + error("Less than an exclusive minimum " + minimum, propertyValue, JsonValidationError.LOW_PRIORITY); } } else { if (value.doubleValue() < doubleValue) { - error("Less than a minimum " + minimum, propertyValue); + error("Less than a minimum " + minimum, propertyValue, JsonValidationError.LOW_PRIORITY); } } } @@ -834,10 +842,11 @@ class JsonSchemaAnnotatorChecker { // returns the schema, selected for annotation private JsonSchemaObject processOneOf(@NotNull JsonValueAdapter value, List oneOf) { - final Map errors = new HashMap<>(); + final List candidateErroneousCheckers = ContainerUtil.newArrayList(); + final List candidateErroneousSchemas = ContainerUtil.newArrayList(); boolean wasTypeError = false; final List correct = new SmartList<>(); - JsonSchemaObject current = null; + int errorCount = 0; for (JsonSchemaObject object : oneOf) { // skip it if something JS awaited, we do not process it currently if (object.isShouldValidateAgainstJSType()) continue; @@ -846,16 +855,17 @@ class JsonSchemaAnnotatorChecker { checker.checkByScheme(value, object); if (checker.isCorrect()) { - current = object; - errors.clear(); + candidateErroneousCheckers.clear(); + candidateErroneousSchemas.clear(); correct.add(object); + errorCount = 0; } else { - if (errors.isEmpty() || wasTypeError && !checker.isHadTypeError() || errors.size() > checker.getErrors().size()) { + if (errorCount == 0 || wasTypeError && !checker.isHadTypeError() || errorCount > checker.getErrors().size()) { + candidateErroneousCheckers.add(checker); + candidateErroneousSchemas.add(object); + errorCount = checker.getErrors().size(); wasTypeError = checker.isHadTypeError(); - current = object; - errors.clear(); - errors.putAll(checker.getErrors()); } } } @@ -865,28 +875,51 @@ class JsonSchemaAnnotatorChecker { if (type != null) { // also check maybe some currently not checked properties like format are different with schemes if (!schemesDifferWithNotCheckedProperties(correct)) { - error("Validates to more than one variant", value.getDelegate()); + error("Validates to more than one variant", value.getDelegate(), JsonValidationError.MEDIUM_PRIORITY); } } + return ContainerUtil.getLastItem(correct); } - else { - if (!errors.isEmpty()) { - for (Map.Entry entry : errors.entrySet()) { - error(entry.getKey(), entry.getValue()); - } - } - } - return current; + + return showErrorsAndGetLeastErroneous(candidateErroneousCheckers, candidateErroneousSchemas); } private static boolean schemesDifferWithNotCheckedProperties(@NotNull final List list) { return list.stream().anyMatch(s -> !StringUtil.isEmptyOrSpaces(s.getFormat())); } + private enum Average { + Light, + Medium, + Hard + } + + @NotNull + private static Average getAverage(@NotNull JsonSchemaAnnotatorChecker checker) { + int lowPriorityCount = 0; + boolean hasMedium = false; + Collection values = checker.getErrors().values(); + for (JsonValidationError value: values) { + switch (value.getPriority()) { + case JsonValidationError.LOW_PRIORITY: + lowPriorityCount++; + break; + case JsonValidationError.MEDIUM_PRIORITY: + hasMedium = true; + break; + case JsonValidationError.HIGH_PRIORITY: + return Average.Hard; + } + } + + return lowPriorityCount <= 3 && !hasMedium ? Average.Light : Average.Medium; + } + // returns the schema, selected for annotation private JsonSchemaObject processAnyOf(@NotNull JsonValueAdapter value, List anyOf) { - final Map errors = new HashMap<>(); - JsonSchemaObject current = null; + final List candidateErroneousCheckers = ContainerUtil.newArrayList(); + final List candidateErroneousSchemas = ContainerUtil.newArrayList(); + for (JsonSchemaObject object : anyOf) { final JsonSchemaAnnotatorChecker checker = new JsonSchemaAnnotatorChecker(); checker.checkByScheme(value, object); @@ -894,17 +927,44 @@ class JsonSchemaAnnotatorChecker { return object; } // maybe we still find the correct schema - continue to iterate - if (errors.isEmpty() && !checker.isHadTypeError()) { - current = object; - errors.clear(); - errors.putAll(checker.getErrors()); + if (!checker.isHadTypeError()) { + candidateErroneousCheckers.add(checker); + candidateErroneousSchemas.add(object); } } - if (!errors.isEmpty()) { - for (Map.Entry entry : errors.entrySet()) { - error(entry.getKey(), entry.getValue()); + + return showErrorsAndGetLeastErroneous(candidateErroneousCheckers, candidateErroneousSchemas); + } + + /** + * Filters schema validation results to get the result with the "minimal" amount of errors. + * This is needed in case of oneOf or anyOf conditions, when there exist no match. + * I.e., when we have multiple schema candidates, but none is applicable. + * In this case we need to show the most "suitable" error messages + * - by detecting the most "likely" schema corresponding to the current entity + */ + @NotNull + private JsonSchemaObject showErrorsAndGetLeastErroneous(@NotNull List candidateErroneousCheckers, + @NotNull List candidateErroneousSchemas) { + JsonSchemaObject current = null; + Optional minAverage = candidateErroneousCheckers.stream().map(c -> getAverage(c)).min(Comparator.comparingInt(c -> c.ordinal())); + int min = minAverage.orElse(Average.Hard).ordinal(); + + for (int i = 0; i < candidateErroneousCheckers.size(); i++) { + JsonSchemaAnnotatorChecker checker = candidateErroneousCheckers.get(i); + if (getAverage(checker).ordinal() <= min) { + current = candidateErroneousSchemas.get(i); + for (Map.Entry entry: checker.getErrors().entrySet()) { + error(entry.getKey(), entry.getValue()); + } } } + + if (current == null) { + current = ContainerUtil.getLastItem(candidateErroneousSchemas); + } + + //noinspection ConstantConditions return current; } diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonValidationError.java b/json/src/com/jetbrains/jsonSchema/impl/JsonValidationError.java index 25ab24a9ac31..ed81a3ef2479 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonValidationError.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonValidationError.java @@ -7,6 +7,7 @@ import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker; import com.jetbrains.jsonSchema.impl.fixes.AddMissingPropertyFix; import com.jetbrains.jsonSchema.impl.fixes.RemoveProhibitedPropertyFix; import com.jetbrains.jsonSchema.impl.fixes.SuggestEnumValuesFix; +import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.Nullable; import java.util.Collection; @@ -19,6 +20,11 @@ public class JsonValidationError { return myIssueData; } + @MagicConstant(flagsFromClass = JsonValidationError.class) + public int getPriority() { + return myPriority; + } + public enum FixableIssueKind { MissingProperty, ProhibitedProperty, @@ -90,14 +96,21 @@ public class JsonValidationError { } } + public static final int LOW_PRIORITY = 3; + public static final int MEDIUM_PRIORITY = 2; + public static final int HIGH_PRIORITY = 1; + private final String myMessage; private final FixableIssueKind myFixableIssueKind; private final IssueData myIssueData; + private final int myPriority; - public JsonValidationError(String message, FixableIssueKind fixableIssueKind, IssueData issueData) { + public JsonValidationError(String message, FixableIssueKind fixableIssueKind, IssueData issueData, + @MagicConstant(flagsFromClass = JsonValidationError.class) int priority) { myMessage = message; myFixableIssueKind = fixableIssueKind; myIssueData = issueData; + myPriority = priority; } public String getMessage() { diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java index 06938b5888ca..e6edcffb01a6 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java @@ -841,4 +841,28 @@ public class JsonSchemaHighlightingTest extends JsonSchemaHighlightingTestBase { " }\n" + "}"); } + + public void testOneOfBestChoiceSchema() throws Exception { + @Language("JSON") String schemaText = FileUtil.loadFile(new File(getTestDataPath() + "/oneOfBestChoiceSchema.json")); + doTest(schemaText, "{\n" + + " \"results\": [\n" + + " {\n" + + " \"type\": \"person\"\n" + + " }\n" + + " ]\n" + + "}"); + } + + public void testAnyOfBestChoiceSchema() throws Exception { + @Language("JSON") String schemaText = FileUtil.loadFile(new File(getTestDataPath() + "/anyOfBestChoiceSchema.json")); + doTest(schemaText, "[\n" + + " {\n" + + " \"directory\": \"/test\",\n" + + " \"arguments\": [\n" + + " \"a\"\n" + + " ],\n" + + " \"file\": \"\"\n" + + " }\n" + + "] "); + } } diff --git a/json/tests/testData/jsonSchema/highlighting/anyOfBestChoiceSchema.json b/json/tests/testData/jsonSchema/highlighting/anyOfBestChoiceSchema.json new file mode 100644 index 000000000000..279d40c574b9 --- /dev/null +++ b/json/tests/testData/jsonSchema/highlighting/anyOfBestChoiceSchema.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://clang.llvm.org/docs/JSONCompilationDatabase.html!", + "title": "Compilation Database", + "description": "List of compilation commands, that specify each translation unit in a project", + "type": "array", + + "items": { + "$ref": "#/definitions/commandObject" + }, + + "definitions": { + "commandObject": { + "description": "Compilation command for single translation unit", + "type": "object", + + "properties": { + + "directory": { + "description": "Working directory of the compilation", + "type": "string", + "minLength": 1, + "examples": [ + "/home/user" + ] + }, + + "file": { + "description": "Main translation unit source processed by this compilation command", + "type": "string", + "minLength": 1, + "examples": [ + "main.cpp" + ] + }, + + "command": { + "description": "Compilation command", + "type": "string", + "minLength": 1, + "examples": [ + "/usr/bin/gcc main.cpp" + ] + }, + + "arguments": { + "description": "Compilation command as list of strings", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + }, + "examples": [ + ["/usr/bin/gcc", "main.cpp"] + ] + }, + + "output": { + "description": "Name of the output created by this compilation command", + "type": "string" + } + }, + + "required": ["directory", "file"], + "anyOf": [ + { + "required": ["command"] + }, + { + "required": ["arguments"] + } + ] + } + } +} diff --git a/json/tests/testData/jsonSchema/highlighting/oneOfBestChoiceSchema.json b/json/tests/testData/jsonSchema/highlighting/oneOfBestChoiceSchema.json new file mode 100644 index 000000000000..61c57f0076f6 --- /dev/null +++ b/json/tests/testData/jsonSchema/highlighting/oneOfBestChoiceSchema.json @@ -0,0 +1,36 @@ +{ + "type": "object", + "required": [ "results" ], + "properties": { + "results": { + "type": "array", + "items": { + "oneOf": [ + { "$ref": "#/definitions/person" }, + { "$ref": "#/definitions/company" } + ] + } + } + }, + + "definitions": { + "person": { + "type": "object", + "properties": { + "type": { "enum": [ "person" ] }, + "name": {"type": "string" }, + "dateOfBirth": {"type":"string"} + }, + "required": [ "type", "name", "dateOfBirth" ], + "additionalProperties": false + }, + + "company": { + "type": "object", + "properties": { + "type": { "enum": [ "company" ] } + }, + "additionalProperties": false + } + } +}