WEB-24726 JSON Schema: highlight unresolved $ref references

This commit is contained in:
Anton Lobov
2018-09-26 10:14:57 +02:00
parent a73d873e99
commit eb5db5342f
13 changed files with 172 additions and 37 deletions
@@ -0,0 +1,5 @@
<html>
<body>
This inspection checks that '$ref' and '$schema' paths are valid<br>
</body>
</html>
@@ -25,7 +25,7 @@ inspection.compliance.option.trailing.comma=Warn about trailing commas
inspection.compliance.option.nan.infinity=Warn about NaN and Infinity/-Infinity numeric values
inspection.duplicate.keys.name=Duplicate keys in object literals
inspection.duplicate.keys.msg.duplicate.keys=Object contains duplicate keys "{0}"
inspection.duplicate.keys.msg.duplicate.keys=Object contains duplicate keys ''{0}''
# Formatter
formatter.align.properties.caption=Align
@@ -51,6 +51,12 @@ json.schema.file.not.found=File not found
json.schema.inspection.compliance.name=Compliance with JSON schema
json.schema.inspection.case.insensitive.enum=Case insensitive matching for enum values
json.schema.ref.refs.inspection.name=Unresolved '$ref' and '$schema' references
json.schema.ref.file.not.found=File ''{0}'' not found
json.schema.ref.cannot.resolve.path=Cannot resolve path ''{0}''
json.schema.ref.no.array.element=Array doesn''t contain element with index '{0}'
json.schema.ref.no.property=Property ''{0}'' not found
settings.json.schema.add.mapping=Add mapping
settings.json.schema.edit.mapping=Edit mapping
settings.json.schema.remove.mapping=Remove mapping
@@ -0,0 +1,46 @@
// 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.inspections;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.json.psi.JsonFile;
import com.intellij.json.psi.JsonValue;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.util.ObjectUtils;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class JsonSchemaBasedInspectionBase extends LocalInspectionTool {
@NotNull
@Override
public HighlightDisplayLevel getDefaultLevel() {
return HighlightDisplayLevel.WARNING;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
PsiFile file = holder.getFile();
JsonValue root = file instanceof JsonFile ? ObjectUtils.tryCast(file.getFirstChild(), JsonValue.class) : null;
if (root == null) return PsiElementVisitor.EMPTY_VISITOR;
JsonSchemaService service = JsonSchemaService.Impl.get(file.getProject());
VirtualFile virtualFile = file.getViewProvider().getVirtualFile();
if (!service.isApplicableToFile(virtualFile)) return PsiElementVisitor.EMPTY_VISITOR;
final JsonSchemaObject rootSchema = service.getSchemaObject(virtualFile);
return doBuildVisitor(root, rootSchema, service, holder, session);
}
protected abstract PsiElementVisitor doBuildVisitor(@NotNull JsonValue root,
@Nullable JsonSchemaObject schema,
@NotNull JsonSchemaService service,
@NotNull ProblemsHolder holder,
@NotNull LocalInspectionToolSession session);
}
@@ -1,28 +1,25 @@
// 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;
package com.jetbrains.jsonSchema.impl.inspections;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.json.JsonBundle;
import com.intellij.json.psi.JsonElementVisitor;
import com.intellij.json.psi.JsonFile;
import com.intellij.json.psi.JsonValue;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.util.ObjectUtils;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.jetbrains.jsonSchema.impl.JsonComplianceCheckerOptions;
import com.jetbrains.jsonSchema.impl.JsonSchemaComplianceChecker;
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
public class JsonSchemaComplianceInspection extends LocalInspectionTool {
public class JsonSchemaComplianceInspection extends JsonSchemaBasedInspectionBase {
public boolean myCaseInsensitiveEnum = false;
@Override
@@ -31,24 +28,11 @@ public class JsonSchemaComplianceInspection extends LocalInspectionTool {
return JsonBundle.message("json.schema.inspection.compliance.name");
}
@NotNull
@Override
public HighlightDisplayLevel getDefaultLevel() {
return HighlightDisplayLevel.WARNING;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
PsiFile file = holder.getFile();
JsonValue root = file instanceof JsonFile ? ObjectUtils.tryCast(file.getFirstChild(), JsonValue.class) : null;
if (root == null) return PsiElementVisitor.EMPTY_VISITOR;
JsonSchemaService service = JsonSchemaService.Impl.get(file.getProject());
VirtualFile virtualFile = file.getViewProvider().getVirtualFile();
if (!service.isApplicableToFile(virtualFile)) return PsiElementVisitor.EMPTY_VISITOR;
final JsonSchemaObject rootSchema = service.getSchemaObject(virtualFile);
if (rootSchema == null) return PsiElementVisitor.EMPTY_VISITOR;
protected PsiElementVisitor doBuildVisitor(@NotNull JsonValue root, @Nullable JsonSchemaObject schema, @NotNull JsonSchemaService service,
@NotNull ProblemsHolder holder,
@NotNull LocalInspectionToolSession session) {
if (schema == null) return PsiElementVisitor.EMPTY_VISITOR;
JsonComplianceCheckerOptions options = new JsonComplianceCheckerOptions(myCaseInsensitiveEnum);
return new JsonElementVisitor() {
@@ -56,7 +40,7 @@ public class JsonSchemaComplianceInspection extends LocalInspectionTool {
public void visitElement(PsiElement element) {
if (element == root) {
// perform this only for the root element, because the checker traverses the hierarchy itself
annotate(element, rootSchema, holder, session, options);
annotate(element, schema, holder, session, options);
}
super.visitElement(element);
}
@@ -0,0 +1,89 @@
// 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.inspections;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.json.JsonBundle;
import com.intellij.json.psi.*;
import com.intellij.openapi.paths.WebReference;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiReference;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JsonSchemaRefReferenceInspection extends JsonSchemaBasedInspectionBase {
@Override
@NotNull
public String getDisplayName() {
return JsonBundle.message("json.schema.ref.refs.inspection.name");
}
@Override
protected PsiElementVisitor doBuildVisitor(@NotNull JsonValue root,
@Nullable JsonSchemaObject schema,
@NotNull JsonSchemaService service,
@NotNull ProblemsHolder holder,
@NotNull LocalInspectionToolSession session) {
boolean checkRefs = schema != null && service.isSchemaFile(schema.getSchemaFile());
return new JsonElementVisitor() {
@Override
public void visitElement(PsiElement element) {
if (element == root) {
if (element instanceof JsonObject) {
final JsonProperty schemaProp = ((JsonObject)element).findProperty("$schema");
if (schemaProp != null) {
doCheck(schemaProp.getValue());
}
}
}
super.visitElement(element);
}
@Override
public void visitProperty(@NotNull JsonProperty o) {
if (!checkRefs) return;
if ("$ref".equals(o.getName())) {
doCheck(o.getValue());
}
super.visitProperty(o);
}
private void doCheck(JsonValue value) {
if (!(value instanceof JsonStringLiteral)) return;
for (PsiReference reference : value.getReferences()) {
if (reference instanceof WebReference) continue;
final PsiElement resolved = reference.resolve();
if (resolved == null) {
holder.registerProblem(reference, getReferenceErrorDesc(reference), ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
}
}
}
private String getReferenceErrorDesc(PsiReference reference) {
final String text = reference.getCanonicalText();
if (reference instanceof FileReference) {
final int hash = text.indexOf('#');
return JsonBundle.message("json.schema.ref.file.not.found", hash == -1 ? text : text.substring(0, hash));
}
final int lastSlash = text.lastIndexOf('/');
if (lastSlash == -1) {
return JsonBundle.message("json.schema.ref.cannot.resolve.path", text);
}
final String substring = text.substring(text.lastIndexOf('/') + 1);
try {
Integer.parseInt(substring);
return JsonBundle.message("json.schema.ref.no.array.element", substring);
}
catch (Exception e) {
return JsonBundle.message("json.schema.ref.no.property", substring);
}
}
};
}
}
@@ -25,9 +25,9 @@ import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider;
import com.jetbrains.jsonSchema.extension.JsonSchemaProjectSelfProviderFactory;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.jetbrains.jsonSchema.impl.JsonSchemaComplianceInspection;
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
import com.jetbrains.jsonSchema.impl.JsonSchemaVersion;
import com.jetbrains.jsonSchema.impl.inspections.JsonSchemaComplianceInspection;
import com.jetbrains.jsonSchema.schemaFile.TestJsonSchemaMappingsProjectConfiguration;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
@@ -9,7 +9,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.util.containers.Predicate;
import com.jetbrains.jsonSchema.impl.JsonSchemaComplianceInspection;
import com.jetbrains.jsonSchema.impl.inspections.JsonSchemaComplianceInspection;
import org.intellij.lang.annotations.Language;
import java.io.File;
@@ -10,7 +10,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.ExpectedHighlightingData;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.util.containers.Predicate;
import com.jetbrains.jsonSchema.impl.JsonSchemaComplianceInspection;
import com.jetbrains.jsonSchema.impl.inspections.JsonSchemaComplianceInspection;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
@@ -17,8 +17,8 @@ package com.jetbrains.jsonSchema;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.testFramework.ExpectedHighlightingData;
import com.jetbrains.jsonSchema.impl.JsonSchemaComplianceInspection;
import com.jetbrains.jsonSchema.impl.JsonSchemaVersion;
import com.jetbrains.jsonSchema.impl.inspections.JsonSchemaComplianceInspection;
import java.util.Collections;
@@ -6,7 +6,7 @@ import com.intellij.json.JsonLanguage;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.Predicate;
import com.jetbrains.jsonSchema.impl.JsonSchemaComplianceInspection;
import com.jetbrains.jsonSchema.impl.inspections.JsonSchemaComplianceInspection;
public class JsonSchemaQuickFixTest extends JsonSchemaQuickFixTestBase {
@Override
@@ -15,6 +15,7 @@ import com.jetbrains.jsonSchema.JsonSchemaTestServiceImpl;
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider;
import com.jetbrains.jsonSchema.extension.JsonSchemaProjectSelfProviderFactory;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.jetbrains.jsonSchema.impl.inspections.JsonSchemaComplianceInspection;
import org.junit.Assert;
import java.io.File;
@@ -1,9 +1,9 @@
{
"foo": {
<warning descr="Object contains duplicate keys \\"foo\\"">"foo"</warning>: 1,
<warning descr="Object contains duplicate keys \\"foo\\"">'foo'</warning>: 2,
<warning descr="Object contains duplicate keys \\"foo\\"">foo</warning>: 3,
<warning descr="Object contains duplicate keys \\"foo\\"">"\u0066\u006f\u006f"</warning>: {
<warning descr="Object contains duplicate keys 'foo'">"foo"</warning>: 1,
<warning descr="Object contains duplicate keys 'foo'">'foo'</warning>: 2,
<warning descr="Object contains duplicate keys 'foo'">foo</warning>: 3,
<warning descr="Object contains duplicate keys 'foo'">"\u0066\u006f\u006f"</warning>: {
"foo": 4
}
}
@@ -79,7 +79,11 @@
<localInspection language="JSON" shortName="JsonSchemaCompliance"
bundle="com.intellij.json.JsonBundle" key="json.schema.inspection.compliance.name" groupKey="json.inspection.group"
enabledByDefault="true" level="WARNING"
implementationClass="com.jetbrains.jsonSchema.impl.JsonSchemaComplianceInspection"/>
implementationClass="com.jetbrains.jsonSchema.impl.inspections.JsonSchemaComplianceInspection"/>
<localInspection language="JSON" shortName="JsonSchemaRefReference"
bundle="com.intellij.json.JsonBundle" key="json.schema.ref.refs.inspection.name" groupKey="json.inspection.group"
enabledByDefault="true" level="WARNING"
implementationClass="com.jetbrains.jsonSchema.impl.inspections.JsonSchemaRefReferenceInspection" />
<lang.elementManipulator forClass="com.intellij.json.psi.JsonStringLiteral"
implementationClass="com.intellij.json.psi.JsonStringLiteralManipulator"/>