diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java index bed907fbe097..ce1d8364c090 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java @@ -10,6 +10,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; +import com.intellij.util.ArrayUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.SmartList; import com.intellij.util.ThreeState; @@ -33,14 +34,14 @@ import java.util.stream.Collectors; class JsonSchemaAnnotatorChecker { private static final Set PRIMITIVE_TYPES = ContainerUtil.set(JsonSchemaType._integer, JsonSchemaType._number, JsonSchemaType._boolean, JsonSchemaType._string, JsonSchemaType._null); - private final Map myErrors; + private final Map myErrors; private boolean myHadTypeError; protected JsonSchemaAnnotatorChecker() { myErrors = new HashMap<>(); } - public Map getErrors() { + public Map getErrors() { return myErrors; } @@ -91,8 +92,8 @@ class JsonSchemaAnnotatorChecker { final JsonSchemaAnnotatorChecker checker = new JsonSchemaAnnotatorChecker(); for (JsonSchemaAnnotatorChecker ch: list) { - for (Map.Entry element: ch.myErrors.entrySet()) { - if (skipErrors.contains(element.getValue())) { + for (Map.Entry element: ch.myErrors.entrySet()) { + if (skipErrors.contains(element.getValue().getMessage())) { continue; } checker.myErrors.put(element.getKey(), element.getValue()); @@ -102,21 +103,33 @@ class JsonSchemaAnnotatorChecker { } private void error(final String error, final PsiElement holder) { + error(error, holder, JsonValidationError.FixableIssueKind.None, null); + } + + private void error(final PsiElement newHolder, JsonValidationError error) { + error(error.getMessage(), newHolder, error.getFixableIssueKind(), error.getIssueData()); + } + + private void error(final String error, final PsiElement holder, JsonValidationError.FixableIssueKind fixableIssueKind, JsonValidationError.IssueData data) { if (myErrors.containsKey(holder)) return; - myErrors.put(holder, error); + myErrors.put(holder, new JsonValidationError(error, fixableIssueKind, data)); } 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); + error(String.format("Type is not allowed. Expected: %s.", allowedTypes[0].getName()), value, + JsonValidationError.FixableIssueKind.ProhibitedType, + new JsonValidationError.TypeMismatchIssueData(allowedTypes)); } else { final String typesText = Arrays.stream(allowedTypes) .map(JsonSchemaType::getName) .distinct() .sorted(Comparator.naturalOrder()) .collect(Collectors.joining(", ")); - error(String.format("Type is not allowed. Expected one of: %s.", typesText), value); + error(String.format("Type is not allowed. Expected one of: %s.", typesText), value, + JsonValidationError.FixableIssueKind.ProhibitedType, + new JsonValidationError.TypeMismatchIssueData(allowedTypes)); } } else { error("Type is not allowed", value); @@ -258,7 +271,9 @@ class JsonSchemaAnnotatorChecker { final JsonSchemaVariantsTreeBuilder.Step step = JsonSchemaVariantsTreeBuilder.Step.createPropertyStep(name); final Pair pair = step.step(schema, true); if (ThreeState.NO.equals(pair.getFirst()) && !set.contains(name)) { - error(JsonBundle.message("json.schema.annotation.not.allowed.property", name), property.getDelegate()); + error(JsonBundle.message("json.schema.annotation.not.allowed.property", name), property.getDelegate(), + JsonValidationError.FixableIssueKind.ProhibitedProperty, + new JsonValidationError.ProhibitedPropertyIssueData(name)); } else if (ThreeState.UNSURE.equals(pair.getFirst()) && property.getValue() != null) { checkObjectBySchemaRecordErrors(pair.getSecond(), property.getValue()); @@ -271,7 +286,9 @@ class JsonSchemaAnnotatorChecker { if (required != null) { for (String req : required) { if (!set.contains(req)) { - error("Missing required property '" + req + "'", value.getDelegate()); + error("Missing required property '" + req + "'", value.getDelegate(), + JsonValidationError.FixableIssueKind.MissingProperty, + new JsonValidationError.MissingPropertyIssueData(req, resolvePropertyType(schema, req))); } } } @@ -289,7 +306,9 @@ class JsonSchemaAnnotatorChecker { for (String s : list) { if (!set.contains(s)) { error("Dependency is violated: '" + s + "' must be specified, since '" + entry.getKey() + "' is specified", - value.getDelegate()); + value.getDelegate(), + JsonValidationError.FixableIssueKind.MissingProperty, + new JsonValidationError.MissingPropertyIssueData(s, resolvePropertyType(schema, s))); } } } @@ -308,6 +327,27 @@ class JsonSchemaAnnotatorChecker { validateAsJsonSchema(object.getDelegate()); } + @Nullable + private static JsonSchemaType resolvePropertyType(@NotNull JsonSchemaObject schema, String req) { + JsonSchemaType type = null; + if (schema.getProperties().containsKey(req)) { + type = schema.getProperties().get(req).getType(); + } + else { + JsonSchemaObject propertySchema = schema.getMatchingPatternPropertySchema(req); + if (propertySchema != null) { + type = propertySchema.getType(); + } + else { + JsonSchemaObject additionalPropertiesSchema = schema.getAdditionalPropertiesSchema(); + if (additionalPropertiesSchema != null) { + type = additionalPropertiesSchema.getType(); + } + } + } + return type; + } + private void validateAsJsonSchema(@NotNull PsiElement objElement) { final JsonObject object = ObjectUtils.tryCast(objElement, JsonObject.class); if (object == null) return; @@ -402,7 +442,9 @@ class JsonSchemaAnnotatorChecker { if (equalsIgnoreQuotesAndCase(object.toString(), text)) return; } } - error("Value should be one of: [" + StringUtil.join(objects, o -> o.toString(), ", ") + "]", value); + error("Value should be one of: [" + StringUtil.join(objects, o -> o.toString(), ", ") + "]", value, + JsonValidationError.FixableIssueKind.NonEnumValue, + new JsonValidationError.NonEnumValueIssueData(ContainerUtil.map(objects, o -> o.toString()).toArray(ArrayUtil.EMPTY_STRING_ARRAY))); } private static boolean equalsIgnoreQuotesAndCase(@NotNull final String s1, @NotNull final String s2) { @@ -629,7 +671,9 @@ class JsonSchemaAnnotatorChecker { value = Integer.valueOf(propValue.getText()); } catch (NumberFormatException e) { - error("Integer value expected", propValue); + error("Integer value expected", propValue, + JsonValidationError.FixableIssueKind.TypeMismatch, + new JsonValidationError.TypeMismatchIssueData(new JsonSchemaType[]{schemaType})); return; } } @@ -638,7 +682,9 @@ class JsonSchemaAnnotatorChecker { value = Double.valueOf(propValue.getText()); } catch (NumberFormatException e) { - error("Double value expected", propValue); + error("Double value expected", propValue, + JsonValidationError.FixableIssueKind.TypeMismatch, + new JsonValidationError.TypeMismatchIssueData(new JsonSchemaType[]{schemaType})); return; } } @@ -758,7 +804,7 @@ class JsonSchemaAnnotatorChecker { // returns the schema, selected for annotation private JsonSchemaObject processOneOf(@NotNull JsonValueAdapter value, List oneOf) { - final Map errors = new HashMap<>(); + final Map errors = new HashMap<>(); boolean wasTypeError = false; final List correct = new SmartList<>(); JsonSchemaObject current = null; @@ -795,8 +841,8 @@ class JsonSchemaAnnotatorChecker { } else { if (!errors.isEmpty()) { - for (Map.Entry entry : errors.entrySet()) { - error(entry.getValue(), entry.getKey()); + for (Map.Entry entry : errors.entrySet()) { + error(entry.getKey(), entry.getValue()); } } } @@ -809,7 +855,7 @@ class JsonSchemaAnnotatorChecker { // returns the schema, selected for annotation private JsonSchemaObject processAnyOf(@NotNull JsonValueAdapter value, List anyOf) { - final Map errors = new HashMap<>(); + final Map errors = new HashMap<>(); JsonSchemaObject current = null; for (JsonSchemaObject object : anyOf) { final JsonSchemaAnnotatorChecker checker = new JsonSchemaAnnotatorChecker(); @@ -825,8 +871,8 @@ class JsonSchemaAnnotatorChecker { } } if (!errors.isEmpty()) { - for (Map.Entry entry : errors.entrySet()) { - error(entry.getValue(), entry.getKey()); + for (Map.Entry entry : errors.entrySet()) { + error(entry.getKey(), entry.getValue()); } } return current; diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaComplianceChecker.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaComplianceChecker.java index 5ba236881f56..3ba554c46100 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaComplianceChecker.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaComplianceChecker.java @@ -2,6 +2,7 @@ package com.jetbrains.jsonSchema.impl; import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; @@ -76,11 +77,17 @@ public class JsonSchemaComplianceChecker { private void createWarnings(@Nullable JsonSchemaAnnotatorChecker checker) { if (checker != null && ! checker.isCorrect()) { - for (Map.Entry entry : checker.getErrors().entrySet()) { + for (Map.Entry entry : checker.getErrors().entrySet()) { if (checkIfAlreadyProcessed(entry.getKey())) continue; - String value = entry.getValue(); + String value = entry.getValue().getMessage(); if (myMessagePrefix != null) value = myMessagePrefix + value; - myHolder.registerProblem(entry.getKey(), value); + LocalQuickFix fix = entry.getValue().createFix(entry.getKey()); + if (fix == null) { + myHolder.registerProblem(entry.getKey(), value); + } + else { + myHolder.registerProblem(entry.getKey(), value, fix); + } } } } diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonValidationError.java b/json/src/com/jetbrains/jsonSchema/impl/JsonValidationError.java new file mode 100644 index 000000000000..03766940a621 --- /dev/null +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonValidationError.java @@ -0,0 +1,83 @@ +// 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.intellij.codeInspection.LocalQuickFix; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.Nullable; + +public class JsonValidationError { + + public IssueData getIssueData() { + return myIssueData; + } + + public enum FixableIssueKind { + MissingProperty, + ProhibitedProperty, + NonEnumValue, + ProhibitedType, + TypeMismatch, + None + } + + public interface IssueData { + + } + + public static class MissingPropertyIssueData implements IssueData { + public final String propertyName; + public final JsonSchemaType propertyType; + + public MissingPropertyIssueData(String propertyName, JsonSchemaType propertyType) { + this.propertyName = propertyName; + this.propertyType = propertyType; + } + } + + public static class ProhibitedPropertyIssueData implements IssueData { + public final String propertyName; + + public ProhibitedPropertyIssueData(String propertyName) { + this.propertyName = propertyName; + } + } + + public static class NonEnumValueIssueData implements IssueData { + public final String[] expectedValues; + + public NonEnumValueIssueData(String[] expectedValues) { + this.expectedValues = expectedValues; + } + } + + public static class TypeMismatchIssueData implements IssueData { + public final JsonSchemaType[] expectedTypes; + + public TypeMismatchIssueData(JsonSchemaType[] expectedTypes) { + this.expectedTypes = expectedTypes; + } + } + + private final String myMessage; + private final FixableIssueKind myFixableIssueKind; + private final IssueData myIssueData; + + public JsonValidationError(String message, FixableIssueKind fixableIssueKind, IssueData issueData) { + myMessage = message; + myFixableIssueKind = fixableIssueKind; + myIssueData = issueData; + } + + public String getMessage() { + return myMessage; + } + + public FixableIssueKind getFixableIssueKind() { + return myFixableIssueKind; + } + + @Nullable + public LocalQuickFix createFix(PsiElement key) { + return null; + } +}