prepare for WEB-32826 JSON schema: Provide quick fixes

This commit is contained in:
Anton Lobov
2018-05-26 16:47:51 +02:00
parent d9c95771a5
commit f181668be4
3 changed files with 158 additions and 22 deletions
@@ -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<JsonSchemaType> PRIMITIVE_TYPES =
ContainerUtil.set(JsonSchemaType._integer, JsonSchemaType._number, JsonSchemaType._boolean, JsonSchemaType._string, JsonSchemaType._null);
private final Map<PsiElement, String> myErrors;
private final Map<PsiElement, JsonValidationError> myErrors;
private boolean myHadTypeError;
protected JsonSchemaAnnotatorChecker() {
myErrors = new HashMap<>();
}
public Map<PsiElement, String> getErrors() {
public Map<PsiElement, JsonValidationError> getErrors() {
return myErrors;
}
@@ -91,8 +92,8 @@ class JsonSchemaAnnotatorChecker {
final JsonSchemaAnnotatorChecker checker = new JsonSchemaAnnotatorChecker();
for (JsonSchemaAnnotatorChecker ch: list) {
for (Map.Entry<PsiElement, String> element: ch.myErrors.entrySet()) {
if (skipErrors.contains(element.getValue())) {
for (Map.Entry<PsiElement, JsonValidationError> 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<ThreeState, JsonSchemaObject> 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<JsonSchemaObject> oneOf) {
final Map<PsiElement, String> errors = new HashMap<>();
final Map<PsiElement, JsonValidationError> errors = new HashMap<>();
boolean wasTypeError = false;
final List<JsonSchemaObject> correct = new SmartList<>();
JsonSchemaObject current = null;
@@ -795,8 +841,8 @@ class JsonSchemaAnnotatorChecker {
}
else {
if (!errors.isEmpty()) {
for (Map.Entry<PsiElement, String> entry : errors.entrySet()) {
error(entry.getValue(), entry.getKey());
for (Map.Entry<PsiElement, JsonValidationError> 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<JsonSchemaObject> anyOf) {
final Map<PsiElement, String> errors = new HashMap<>();
final Map<PsiElement, JsonValidationError> 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<PsiElement, String> entry : errors.entrySet()) {
error(entry.getValue(), entry.getKey());
for (Map.Entry<PsiElement, JsonValidationError> entry : errors.entrySet()) {
error(entry.getKey(), entry.getValue());
}
}
return current;
@@ -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<PsiElement, String> entry : checker.getErrors().entrySet()) {
for (Map.Entry<PsiElement, JsonValidationError> 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);
}
}
}
}
@@ -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;
}
}