WEB-27538 JSON Schema: quick documentation improvements

This commit is contained in:
Irina.Chernushina
2017-06-30 11:04:51 +02:00
parent 8c933bf3aa
commit 8799713ad4
18 changed files with 128 additions and 36 deletions
@@ -22,6 +22,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.Consumer;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider;
@@ -210,13 +211,13 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
}
private void addPropertyVariant(@NotNull String key, @NotNull JsonSchemaObject jsonSchemaObject, boolean hasValue, boolean insertComma) {
final String description = jsonSchemaObject.getDescription();
final String title = jsonSchemaObject.getTitle();
jsonSchemaObject = ObjectUtils.coalesce(ContainerUtil.getFirstItem(new JsonSchemaResolver(jsonSchemaObject).resolve()),
jsonSchemaObject);
key = !myWrapInQuotes ? key : StringUtil.wrapWithDoubleQuote(key);
LookupElementBuilder builder = LookupElementBuilder.create(key);
String typeText = StringUtil.isEmpty(title) ? description : title;
if (!StringUtil.isEmpty(typeText)) {
final String typeText = StringUtil.escapeXml(jsonSchemaObject.getDocumentation(true));
if (!StringUtil.isEmptyOrSpaces(typeText)) {
builder = builder.withTypeText(typeText, true);
}
@@ -1,15 +1,11 @@
package com.jetbrains.jsonSchema.impl;
import com.intellij.json.psi.JsonObject;
import com.intellij.json.psi.JsonProperty;
import com.intellij.json.psi.JsonStringLiteral;
import com.intellij.json.psi.JsonValue;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import org.jetbrains.annotations.NotNull;
@@ -23,7 +19,7 @@ public class JsonSchemaDocumentationProvider implements DocumentationProvider {
@Nullable
@Override
public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
return null;
return findSchemaAndGenerateDoc(element, originalElement, true);
}
@Nullable
@@ -35,21 +31,24 @@ public class JsonSchemaDocumentationProvider implements DocumentationProvider {
@Nullable
@Override
public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
return findSchemaAndGenerateDoc(element, originalElement, false);
}
@Nullable
private static String findSchemaAndGenerateDoc(PsiElement element, @Nullable PsiElement originalElement, final boolean preferShort) {
element = ObjectUtils.coalesce(originalElement, element);
final PsiFile containingFile = element.getContainingFile();
if (containingFile == null) return null;
final JsonSchemaService service = JsonSchemaService.Impl.get(element.getProject());
final JsonSchemaObject rootSchema = service.getSchemaObject(containingFile.getViewProvider().getVirtualFile());
if (rootSchema == null) return null;
if (JsonSchemaService.isSchemaFile(containingFile)) {
return generateForJsonSchemaFileType(element);
}
return generateDoc(element, rootSchema);
return generateDoc(element, rootSchema, preferShort);
}
@Nullable
public static String generateDoc(@NotNull final PsiElement element,
@NotNull final JsonSchemaObject rootSchema) {
@NotNull final JsonSchemaObject rootSchema, final boolean preferShort) {
final JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(element, rootSchema);
if (walker == null) return null;
@@ -59,24 +58,8 @@ public class JsonSchemaDocumentationProvider implements DocumentationProvider {
final Collection<JsonSchemaObject> schemas = new JsonSchemaResolver(rootSchema, true, position).resolve();
return schemas.stream().filter(schema -> !StringUtil.isEmptyOrSpaces(schema.getDescription()))
.findFirst().map(JsonSchemaObject::getDescription).orElse(null);
}
@Nullable
private static String generateForJsonSchemaFileType(@NotNull PsiElement element) {
final JsonProperty jsonProperty =
element instanceof JsonProperty ? (JsonProperty)element : PsiTreeUtil.getParentOfType(element, JsonProperty.class);
if (jsonProperty != null) {
final JsonValue value = jsonProperty.getValue();
if (value instanceof JsonObject) {
final JsonProperty description = ((JsonObject)value).findProperty("description");
if (description != null && description.getValue() instanceof JsonStringLiteral) {
return StringUtil.escapeXml(StringUtil.unquoteString(description.getValue().getText()));
}
}
}
return null;
return StringUtil.escapeXml(schemas.stream().filter(schema -> !StringUtil.isEmptyOrSpaces(schema.getDocumentation(preferShort)))
.findFirst().map(schema -> schema.getDocumentation(preferShort)).orElse(null));
}
@Nullable
@@ -87,7 +87,7 @@ public class JsonSchemaObject {
// peer pointer is not merged!
public void mergeValues(JsonSchemaObject other) {
// we do not copy id, schema, title and description
// we do not copy id, schema
myProperties.putAll(other.myProperties);
myDefinitionsMap = copyMap(myDefinitionsMap, other.myDefinitionsMap);
@@ -95,6 +95,9 @@ public class JsonSchemaObject {
other.myPatternProperties == null ? null : other.myPatternProperties.mySchemasMap);
myPatternProperties = map == null ? null : new PatternProperties(map);
if (!StringUtil.isEmptyOrSpaces(other.myTitle)) {
myTitle = other.myTitle;
}
if (!StringUtil.isEmptyOrSpaces(other.myDescription)) {
myDescription = other.myDescription;
}
@@ -548,6 +551,12 @@ public class JsonSchemaObject {
return current;
}
@Nullable
public String getDocumentation(final boolean preferShort) {
if (preferShort) return StringUtil.isEmptyOrSpaces(myTitle) ? myDescription : myTitle;
return StringUtil.isEmptyOrSpaces(myDescription) ? myTitle : myDescription;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -54,6 +54,7 @@ public class JsonSchemaResolver {
return MatchResult.create(node);
}
@NotNull
public Collection<JsonSchemaObject> resolve() {
return resolve(false, false, false);
}
@@ -20,8 +20,10 @@ import com.intellij.json.JsonLanguage;
import com.intellij.lang.LanguageDocumentation;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiUtilBase;
import com.jetbrains.jsonSchema.impl.JsonSchemaDocumentationProvider;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.ArrayList;
@@ -52,7 +54,8 @@ public abstract class JsonBySchemaDocumentationBaseTest extends JsonSchemaHeavyA
@Override
public void doCheck() {
PsiElement psiElement = DocumentationManager.getInstance(getProject()).findTargetElement(myEditor, myFile);
final PsiElement psiElement = PsiUtilBase.getElementAtCaret(myEditor);
Assert.assertNotNull(psiElement);
assertDocumentation(psiElement, psiElement, hasDoc);
}
});
@@ -65,6 +68,12 @@ public abstract class JsonBySchemaDocumentationBaseTest extends JsonSchemaHeavyA
protected void assertDocumentation(@NotNull PsiElement docElement, @NotNull PsiElement context, boolean shouldHaveDoc) {
DocumentationProvider documentationProvider = DocumentationManager.getProviderFromElement(context);
String inlineDoc = documentationProvider.generateDoc(docElement, context);
String quickNavigate = documentationProvider.getQuickNavigateInfo(docElement, context);
checkExpectedDoc(shouldHaveDoc, inlineDoc, false);
checkExpectedDoc(shouldHaveDoc, quickNavigate, true);
}
private void checkExpectedDoc(boolean shouldHaveDoc, String inlineDoc, boolean preferShort) {
if (shouldHaveDoc) {
assertNotNull("inline help is null", inlineDoc);
}
@@ -72,7 +81,7 @@ public abstract class JsonBySchemaDocumentationBaseTest extends JsonSchemaHeavyA
assertNull("inline help is not null", inlineDoc);
}
if (shouldHaveDoc) {
assertSameLinesWithFile(getTestDataPath() + "/" + getTestName(true) + ".html", inlineDoc);
assertSameLinesWithFile(getTestDataPath() + "/" + getTestName(true) + (preferShort ? "_short.html" : ".html"), inlineDoc);
}
}
}
@@ -32,4 +32,12 @@ public class JsonSchemaDocumentationTest extends JsonBySchemaDocumentationBaseTe
public void testCheckEscaping() throws Exception {
doTest(true, "json");
}
public void testWithDefinition() throws Exception {
doTest(true, "json");
}
public void testWithTitleInDefinition() throws Exception {
doTest(true, "json");
}
}
@@ -1,7 +1,9 @@
package com.jetbrains.jsonSchema.impl;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.jetbrains.jsonSchema.JsonSchemaHighlightingTest;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
/**
* @author Irina.Chernushina on 10/1/2015.
@@ -157,6 +159,47 @@ public class JsonBySchemaCompletionTest extends JsonBySchemaCompletionBaseTest {
testImpl(schema, "{\"null\": <caret>}", "false", "null", "true");
}
public void testDescriptionFromDefinitionInCompletion() throws Exception {
final String schema = "{\n" +
" \"definitions\": {\n" +
" \"target\": {\n" +
" \"description\": \"Target description\"\n" +
" }\n" +
" },\n" +
" \"properties\": {\n" +
" \"source\": {\n" +
" \"$ref\": \"#/definitions/target\"\n" +
" }\n" +
" }\n" +
"}";
testImpl(schema, "{<caret>}", "\"source\"");
Assert.assertEquals(1, myItems.length);
final LookupElementPresentation presentation = new LookupElementPresentation();
myItems[0].renderElement(presentation);
Assert.assertEquals("Target description", presentation.getTypeText());
}
public void testDescriptionFromTitleInCompletion() throws Exception {
final String schema = "{\n" +
" \"definitions\": {\n" +
" \"target\": {\n" +
" \"title\": \"Target title\",\n" +
" \"description\": \"Target description\"\n" +
" }\n" +
" },\n" +
" \"properties\": {\n" +
" \"source\": {\n" +
" \"$ref\": \"#/definitions/target\"\n" +
" }\n" +
" }\n" +
"}";
testImpl(schema, "{<caret>}", "\"source\"");
Assert.assertEquals(1, myItems.length);
final LookupElementPresentation presentation = new LookupElementPresentation();
myItems[0].renderElement(presentation);
Assert.assertEquals("Target title", presentation.getTypeText());
}
@NotNull
private static String parcelShopSchema() {
return "{\n" +
@@ -0,0 +1 @@
Disallows /// &lt;reference path=&gt; imports (use ES6-style imports instead)
@@ -0,0 +1 @@
my second level property description 1
@@ -0,0 +1 @@
my property description 1
@@ -0,0 +1 @@
Description found through definition
@@ -0,0 +1,3 @@
{
"<caret>source": {}
}
@@ -0,0 +1,12 @@
{
"definitions": {
"target": {
"description": "Description found through definition"
}
},
"properties": {
"source": {
"$ref": "#/definitions/target"
}
}
}
@@ -0,0 +1 @@
Description found through definition
@@ -0,0 +1 @@
Description found through definition
@@ -0,0 +1,3 @@
{
"<caret>source": {}
}
@@ -0,0 +1,13 @@
{
"definitions": {
"target": {
"title": "Title found through definition",
"description": "Description found through definition"
}
},
"properties": {
"source": {
"$ref": "#/definitions/target"
}
}
}
@@ -0,0 +1 @@
Title found through definition