diff --git a/json/resources/inspectionDescriptions/JsonSchemaRefReference.html b/json/resources/inspectionDescriptions/JsonSchemaRefReference.html new file mode 100644 index 000000000000..714ecb28b485 --- /dev/null +++ b/json/resources/inspectionDescriptions/JsonSchemaRefReference.html @@ -0,0 +1,5 @@ + + +This inspection checks that '$ref' and '$schema' paths are valid
+ + \ No newline at end of file diff --git a/json/src/com/intellij/json/JsonBundle.properties b/json/src/com/intellij/json/JsonBundle.properties index 6726f8ef7cfa..a235cff493c8 100644 --- a/json/src/com/intellij/json/JsonBundle.properties +++ b/json/src/com/intellij/json/JsonBundle.properties @@ -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 \ No newline at end of file diff --git a/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaBasedInspectionBase.java b/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaBasedInspectionBase.java new file mode 100644 index 000000000000..0a47e699b722 --- /dev/null +++ b/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaBasedInspectionBase.java @@ -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); +} diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaComplianceInspection.java b/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaComplianceInspection.java similarity index 62% rename from json/src/com/jetbrains/jsonSchema/impl/JsonSchemaComplianceInspection.java rename to json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaComplianceInspection.java index 492c5190480e..0ff947322fa8 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaComplianceInspection.java +++ b/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaComplianceInspection.java @@ -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); } diff --git a/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaRefReferenceInspection.java b/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaRefReferenceInspection.java new file mode 100644 index 000000000000..59cd920fee91 --- /dev/null +++ b/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaRefReferenceInspection.java @@ -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); + } + } + }; + } +} diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java index ff6083cb7c82..ec3e0bd23a8b 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java @@ -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; diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java index d5038ded0218..06317d9fc080 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java @@ -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; diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaReSharperHighlightingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaReSharperHighlightingTest.java index a61618e13573..b901387286b2 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaReSharperHighlightingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaReSharperHighlightingTest.java @@ -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; diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaSelfHighligthingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaSelfHighligthingTest.java index 1cf5375ca58a..29a5cabc32aa 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaSelfHighligthingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaSelfHighligthingTest.java @@ -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; diff --git a/json/tests/test/com/jetbrains/jsonSchema/fixes/JsonSchemaQuickFixTest.java b/json/tests/test/com/jetbrains/jsonSchema/fixes/JsonSchemaQuickFixTest.java index 44e88f4eb6cb..4274441e3fc2 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/fixes/JsonSchemaQuickFixTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/fixes/JsonSchemaQuickFixTest.java @@ -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 diff --git a/json/tests/test/com/jetbrains/jsonSchema/impl/JsonSchemaReadTest.java b/json/tests/test/com/jetbrains/jsonSchema/impl/JsonSchemaReadTest.java index cb44733f4f5e..5600c3d07d63 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/impl/JsonSchemaReadTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/impl/JsonSchemaReadTest.java @@ -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; diff --git a/json/tests/testData/highlighting/DuplicatePropertyKeys.json b/json/tests/testData/highlighting/DuplicatePropertyKeys.json index 485d44ecca61..d04ac4115814 100644 --- a/json/tests/testData/highlighting/DuplicatePropertyKeys.json +++ b/json/tests/testData/highlighting/DuplicatePropertyKeys.json @@ -1,9 +1,9 @@ { "foo": { - "foo": 1, - 'foo': 2, - foo: 3, - "\u0066\u006f\u006f": { + "foo": 1, + 'foo': 2, + foo: 3, + "\u0066\u006f\u006f": { "foo": 4 } } diff --git a/platform/platform-resources/src/META-INF/JsonPlugin.xml b/platform/platform-resources/src/META-INF/JsonPlugin.xml index 9575d30dea16..5ef48e1d3482 100644 --- a/platform/platform-resources/src/META-INF/JsonPlugin.xml +++ b/platform/platform-resources/src/META-INF/JsonPlugin.xml @@ -79,7 +79,11 @@ + implementationClass="com.jetbrains.jsonSchema.impl.inspections.JsonSchemaComplianceInspection"/> +