RUBY-20082 Apply JSON Schema to YAML

review: IDEA-CR-33311
This commit is contained in:
Anton Lobov
2018-06-15 18:29:07 +02:00
parent 6e63c7d832
commit 79870f8607
86 changed files with 2229 additions and 190 deletions

View File

@@ -3,6 +3,7 @@ package com.jetbrains.jsonSchema.extension;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.psi.PsiElement;
import com.intellij.util.ThreeState;
import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter;
import com.jetbrains.jsonSchema.impl.JsonOriginalPsiWalker;
@@ -21,15 +22,22 @@ import java.util.Set;
public interface JsonLikePsiWalker {
JsonOriginalPsiWalker JSON_ORIGINAL_PSI_WALKER = new JsonOriginalPsiWalker();
boolean isName(PsiElement element);
/**
* Returns YES in place where a property name is expected,
* NO in place where a property value is expected,
* UNSURE where both property name and property value can be present
*/
ThreeState isName(PsiElement element);
boolean isPropertyWithValue(@NotNull PsiElement element);
PsiElement goUpToCheckable(@NotNull final PsiElement element);
@Nullable
List<JsonSchemaVariantsTreeBuilder.Step> findPosition(@NotNull final PsiElement element, boolean forceLastTransition);
boolean isNameQuoted();
boolean onlyDoubleQuotesForStringLiterals();
default boolean quotesForStringLiterals() { return true; }
boolean hasPropertiesBehindAndNoComma(@NotNull PsiElement element);
Set<String> getPropertyNamesOfParentObject(@NotNull PsiElement element);
Set<String> getPropertyNamesOfParentObject(@NotNull PsiElement originalPosition, PsiElement computedPosition);
@Nullable
JsonPropertyAdapter getParentPropertyAdapter(@NotNull PsiElement element);
boolean isTopJsonElement(@NotNull PsiElement element);
@@ -46,4 +54,13 @@ public interface JsonLikePsiWalker {
.map(extension -> extension.create(schemaObject))
.orElse(null);
}
default String getDefaultObjectValue(boolean includeWhitespaces) { return "{}"; }
@Nullable default String defaultObjectValueDescription() { return null; }
default String getDefaultArrayValue(boolean includeWhitespaces) { return "[]"; }
@Nullable default String defaultArrayValueDescription() { return null; }
default boolean invokeEnterBeforeObjectAndArray() { return false; }
default String getNodeTextForValidation(PsiElement element) { return element.getText(); }
}

View File

@@ -28,5 +28,4 @@ public interface JsonPropertyAdapter {
@Nullable JsonValueAdapter getValue();
@NotNull PsiElement getDelegate();
@Nullable JsonObjectValueAdapter getParentObject();
@Nullable JsonArrayValueAdapter getParentArray();
}

View File

@@ -10,6 +10,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ThreeState;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter;
@@ -36,14 +37,14 @@ public class JsonOriginalPsiWalker implements JsonLikePsiWalker {
}
@Override
public boolean isName(PsiElement element) {
public ThreeState isName(PsiElement element) {
final PsiElement parent = element.getParent();
if (parent instanceof JsonObject) {
return true;
return ThreeState.YES;
} else if (parent instanceof JsonProperty) {
return PsiTreeUtil.isAncestor(((JsonProperty)parent).getNameElement(), element, false);
return PsiTreeUtil.isAncestor(((JsonProperty)parent).getNameElement(), element, false) ? ThreeState.YES : ThreeState.NO;
}
return false;
return ThreeState.NO;
}
@Override
@@ -138,8 +139,8 @@ public class JsonOriginalPsiWalker implements JsonLikePsiWalker {
}
@Override
public Set<String> getPropertyNamesOfParentObject(@NotNull PsiElement element) {
final JsonObject object = PsiTreeUtil.getParentOfType(element, JsonObject.class);
public Set<String> getPropertyNamesOfParentObject(@NotNull PsiElement originalPosition, PsiElement computedPosition) {
final JsonObject object = PsiTreeUtil.getParentOfType(originalPosition, JsonObject.class);
if (object != null) {
return object.getPropertyList().stream()
.filter(p -> !isNameQuoted() || p.getNameElement() instanceof JsonStringLiteral)

View File

@@ -147,6 +147,11 @@ class JsonSchemaAnnotatorChecker {
if (JsonSchemaType._boolean.equals(type)) {
checkForEnum(value.getDelegate(), schema);
}
else if (JsonSchemaType._string_number.equals(type)) {
checkNumber(value.getDelegate(), schema, type);
checkString(value.getDelegate(), schema);
checkForEnum(value.getDelegate(), schema);
}
else if (JsonSchemaType._number.equals(type) || JsonSchemaType._integer.equals(type)) {
checkNumber(value.getDelegate(), schema, type);
checkForEnum(value.getDelegate(), schema);
@@ -444,25 +449,25 @@ class JsonSchemaAnnotatorChecker {
if (schema.getEnum() == null || schema.getPattern() != null) return;
final JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(value, schema);
if (walker == null) return;
final String text = StringUtil.notNullize(value.getText());
final String text = StringUtil.notNullize(walker.getNodeTextForValidation(value));
final List<Object> objects = schema.getEnum();
for (Object object : objects) {
if (walker.onlyDoubleQuotesForStringLiterals()) {
if (object.toString().equalsIgnoreCase(text)) return;
}
else {
if (equalsIgnoreQuotesAndCase(object.toString(), text)) return;
if (equalsIgnoreQuotesAndCase(object.toString(), text, walker.quotesForStringLiterals())) return;
}
}
error("Value should be one of: [" + StringUtil.join(objects, o -> o.toString(), ", ") + "]", value,
JsonValidationError.FixableIssueKind.NonEnumValue, null);
}
private static boolean equalsIgnoreQuotesAndCase(@NotNull final String s1, @NotNull final String s2) {
private static boolean equalsIgnoreQuotesAndCase(@NotNull final String s1, @NotNull final String s2, boolean requireQuotedValues) {
final boolean quoted1 = StringUtil.isQuotedString(s1);
final boolean quoted2 = StringUtil.isQuotedString(s2);
if (quoted1 != quoted2) return false;
if (!quoted1) return s1.equalsIgnoreCase(s2);
if (requireQuotedValues && quoted1 != quoted2) return false;
if (requireQuotedValues && !quoted1) return s1.equalsIgnoreCase(s2);
return StringUtil.unquoteString(s1).equalsIgnoreCase(StringUtil.unquoteString(s2));
}
@@ -545,6 +550,11 @@ class JsonSchemaAnnotatorChecker {
if (JsonSchemaType._integer.equals(input) && JsonSchemaType._number.equals(matchType)) {
return input;
}
if (JsonSchemaType._string_number.equals(input) && (JsonSchemaType._number.equals(matchType)
|| JsonSchemaType._integer.equals(matchType)
|| JsonSchemaType._string.equals(matchType))) {
return input;
}
return matchType;
}
}
@@ -575,8 +585,10 @@ class JsonSchemaAnnotatorChecker {
private void checkArrayItems(@NotNull JsonValueAdapter array, @NotNull final List<JsonValueAdapter> list, final JsonSchemaObject schema) {
if (schema.isUniqueItems()) {
final MultiMap<String, JsonValueAdapter> valueTexts = new MultiMap<>();
final JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(array.getDelegate(), schema);
assert walker != null;
for (JsonValueAdapter adapter : list) {
valueTexts.putValue(adapter.getDelegate().getText(), adapter);
valueTexts.putValue(walker.getNodeTextForValidation(adapter.getDelegate()), adapter);
}
for (Map.Entry<String, Collection<JsonValueAdapter>> entry: valueTexts.entrySet()) {
@@ -638,7 +650,9 @@ class JsonSchemaAnnotatorChecker {
}
private void checkString(PsiElement propValue, JsonSchemaObject schema) {
final String value = StringUtil.unquoteString(propValue.getText());
final JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(propValue, schema);
assert walker != null;
final String value = StringUtil.unquoteString(walker.getNodeTextForValidation(propValue));
if (schema.getMinLength() != null) {
if (value.length() < schema.getMinLength()) {
error("String is shorter than " + schema.getMinLength(), propValue);
@@ -677,9 +691,12 @@ class JsonSchemaAnnotatorChecker {
private void checkNumber(PsiElement propValue, JsonSchemaObject schema, JsonSchemaType schemaType) {
Number value;
final JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(propValue, schema);
assert walker != null;
String valueText = walker.getNodeTextForValidation(propValue);
if (JsonSchemaType._integer.equals(schemaType)) {
try {
value = Integer.valueOf(propValue.getText());
value = Integer.valueOf(valueText);
}
catch (NumberFormatException e) {
error("Integer value expected", propValue,
@@ -690,12 +707,14 @@ class JsonSchemaAnnotatorChecker {
}
else {
try {
value = Double.valueOf(propValue.getText());
value = Double.valueOf(valueText);
}
catch (NumberFormatException e) {
error("Double value expected", propValue,
JsonValidationError.FixableIssueKind.TypeMismatch,
new JsonValidationError.TypeMismatchIssueData(new JsonSchemaType[]{schemaType}));
if (!JsonSchemaType._string_number.equals(schemaType)) {
error("Double value expected", propValue,
JsonValidationError.FixableIssueKind.TypeMismatch,
new JsonValidationError.TypeMismatchIssueData(new JsonSchemaType[]{schemaType}));
}
return;
}
}
@@ -840,7 +859,7 @@ class JsonSchemaAnnotatorChecker {
}
}
}
if (correct.size() == 1) return current;
if (correct.size() == 1) return correct.get(0);
if (correct.size() > 0) {
final JsonSchemaType type = JsonSchemaType.getType(value);
if (type != null) {

View File

@@ -18,6 +18,7 @@ import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.actions.EditorActionUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -29,6 +30,7 @@ import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.Consumer;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider;
@@ -131,26 +133,27 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
if (myWalker == null) return;
final PsiElement checkable = myWalker.goUpToCheckable(myPosition);
if (checkable == null) return;
final boolean isName = myWalker.isName(checkable);
final List<JsonSchemaVariantsTreeBuilder.Step> position = myWalker.findPosition(checkable, !isName);
if (position == null || position.isEmpty() && !isName) return;
final ThreeState isName = myWalker.isName(checkable);
final List<JsonSchemaVariantsTreeBuilder.Step> position = myWalker.findPosition(checkable, isName == ThreeState.NO);
if (position == null || position.isEmpty() && isName == ThreeState.NO) return;
final Collection<JsonSchemaObject> schemas = new JsonSchemaResolver(myRootSchema, false, position).resolve();
// too long here, refactor further
schemas.forEach(schema -> {
if (isName) {
if (isName != ThreeState.NO) {
final boolean insertComma = myWalker.hasPropertiesBehindAndNoComma(myPosition);
final boolean hasValue = myWalker.isPropertyWithValue(myPosition.getParent().getParent());
final Collection<String> properties = myWalker.getPropertyNamesOfParentObject(myOriginalPosition);
final Collection<String> properties = myWalker.getPropertyNamesOfParentObject(myOriginalPosition, myPosition);
final JsonPropertyAdapter adapter = myWalker.getParentPropertyAdapter(myOriginalPosition);
final Map<String, JsonSchemaObject> schemaProperties = schema.getProperties();
addAllPropertyVariants(insertComma, hasValue, properties, adapter, schemaProperties);
addIfThenElsePropertyNameVariants(schema, insertComma, hasValue, properties, adapter);
}
else {
suggestValues(schema);
if (isName != ThreeState.YES) {
suggestValues(schema, isName == ThreeState.NO);
}
});
@@ -199,17 +202,17 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
.forEach(name -> addPropertyVariant(name, schemaProperties.get(name), hasValue, insertComma));
}
private void suggestValues(JsonSchemaObject schema) {
suggestValuesForSchemaVariants(schema.getAnyOf());
suggestValuesForSchemaVariants(schema.getOneOf());
suggestValuesForSchemaVariants(schema.getAllOf());
private void suggestValues(JsonSchemaObject schema, boolean isSurelyValue) {
suggestValuesForSchemaVariants(schema.getAnyOf(), isSurelyValue);
suggestValuesForSchemaVariants(schema.getOneOf(), isSurelyValue);
suggestValuesForSchemaVariants(schema.getAllOf(), isSurelyValue);
if (schema.getEnum() != null) {
for (Object o : schema.getEnum()) {
addValueVariant(o.toString(), null);
}
}
else {
else if (isSurelyValue) {
final JsonSchemaType type = schema.getType();
if (type != null) {
suggestByType(schema, type);
@@ -233,9 +236,11 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
} else if (JsonSchemaType._null.equals(type)) {
addValueVariant("null", null);
} else if (JsonSchemaType._array.equals(type)) {
addValueVariant("[]", null, createArrayOrObjectLiteralInsertHandler());
addValueVariant(myWalker.getDefaultArrayValue(true), null,
myWalker.defaultArrayValueDescription(), createArrayOrObjectLiteralInsertHandler(myWalker.invokeEnterBeforeObjectAndArray()));
} else if (JsonSchemaType._object.equals(type)) {
addValueVariant("{}", null, createArrayOrObjectLiteralInsertHandler());
addValueVariant(myWalker.getDefaultObjectValue(true), null,
myWalker.defaultObjectValueDescription(), createArrayOrObjectLiteralInsertHandler(myWalker.invokeEnterBeforeObjectAndArray()));
}
}
@@ -243,18 +248,23 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
Object defaultValue = schema.getDefault();
String defaultValueString = defaultValue == null ? null : defaultValue.toString();
if (!StringUtil.isEmpty(defaultValueString)) {
String quotedValue = defaultValueString;
if (!StringUtil.isQuotedString(quotedValue)) {
quotedValue = StringUtil.wrapWithDoubleQuote(quotedValue);
String normalizedValue = defaultValueString;
boolean shouldQuote = myWalker.quotesForStringLiterals();
boolean isQuoted = StringUtil.isQuotedString(normalizedValue);
if (shouldQuote && !isQuoted) {
normalizedValue = StringUtil.wrapWithDoubleQuote(normalizedValue);
}
addValueVariant(quotedValue, null);
else if (!shouldQuote && isQuoted) {
normalizedValue = StringUtil.unquoteString(normalizedValue);
}
addValueVariant(normalizedValue, null);
}
}
private void suggestValuesForSchemaVariants(List<JsonSchemaObject> list) {
private void suggestValuesForSchemaVariants(List<JsonSchemaObject> list, boolean isSurelyValue) {
if (list != null && list.size() > 0) {
for (JsonSchemaObject schemaObject : list) {
suggestValues(schemaObject);
suggestValues(schemaObject, isSurelyValue);
}
}
}
@@ -268,12 +278,17 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
private void addValueVariant(@NotNull String key, @SuppressWarnings("SameParameterValue") @Nullable final String description) {
addValueVariant(key, description, null);
addValueVariant(key, description, null, null);
}
private void addValueVariant(@NotNull String key, @SuppressWarnings("SameParameterValue") @Nullable final String description,
private void addValueVariant(@NotNull String key,
@SuppressWarnings("SameParameterValue") @Nullable final String description,
@Nullable final String altText,
@Nullable InsertHandler<LookupElement> handler) {
LookupElementBuilder builder = LookupElementBuilder.create(!myWrapInQuotes ? StringUtil.unquoteString(key) : key);
if (altText != null) {
builder = builder.withPresentableText(altText);
}
if (description != null) {
builder = builder.withTypeText(description);
}
@@ -326,12 +341,19 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
return variants.stream().map(JsonSchemaObject::getType).filter(Objects::nonNull).distinct().count() <= 1;
}
private static InsertHandler<LookupElement> createArrayOrObjectLiteralInsertHandler() {
private static InsertHandler<LookupElement> createArrayOrObjectLiteralInsertHandler(boolean newline) {
return new InsertHandler<LookupElement>() {
@Override
public void handleInsert(InsertionContext context, LookupElement item) {
EditorModificationUtil.moveCaretRelatively(context.getEditor(), -1);
AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(context.getEditor(), null);
Editor editor = context.getEditor();
if (!newline) {
EditorModificationUtil.moveCaretRelatively(editor, -1);
}
else {
EditorActionUtil.moveCaretToLineEnd(editor, false, false);
}
AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(editor, null);
}
};
}
@@ -383,48 +405,82 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
if (handleInsideQuotesInsertion(context, editor, hasValue)) return;
if (finalType != null) {
boolean hadEnter;
switch (finalType) {
case _object:
stringToInsert = ":{}" + comma;
EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, false, true, 2);
EditorModificationUtil.insertStringAtCaret(editor, ": ",
false, true, 2);
hadEnter = false;
boolean invokeEnter = myWalker.invokeEnterBeforeObjectAndArray();
if (invokeEnter) {
invokeEnterHandler(editor);
hadEnter = true;
}
stringToInsert = myWalker.getDefaultObjectValue(false) + comma;
EditorModificationUtil.insertStringAtCaret(editor, stringToInsert,
false, true,
hadEnter ? 0 : 1);
if (hadEnter) {
EditorActionUtil.moveCaretToLineEnd(editor, false, false);
}
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
formatInsertedString(context, stringToInsert.length());
EditorActionHandler handler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_ENTER);
handler.execute(editor, editor.getCaretModel().getCurrentCaret(),
DataManager.getInstance().getDataContext(editor.getContentComponent()));
if (!invokeEnter) {
invokeEnterHandler(editor);
}
break;
case _boolean:
String value = String.valueOf(Boolean.TRUE.toString().equals(defaultValueAsString));
stringToInsert = ":" + value + comma;
stringToInsert = ": " + value + comma;
SelectionModel model = editor.getSelectionModel();
EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, false, true, stringToInsert.length() - comma.length());
EditorModificationUtil.insertStringAtCaret(editor, stringToInsert,
false, true, stringToInsert.length() - comma.length());
formatInsertedString(context, stringToInsert.length());
int start = editor.getSelectionModel().getSelectionStart();
model.setSelection(start - value.length(), start);
AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(context.getEditor(), null);
break;
case _array:
stringToInsert = ":[]" + comma;
EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, false, true, 2);
EditorModificationUtil.insertStringAtCaret(editor, ": ",
false, true, 2);
hadEnter = false;
if (myWalker.invokeEnterBeforeObjectAndArray()) {
invokeEnterHandler(editor);
hadEnter = true;
}
stringToInsert = myWalker.getDefaultArrayValue(false) + comma;
EditorModificationUtil.insertStringAtCaret(editor, stringToInsert,
false, true,
hadEnter ? 0 : 1);
if (hadEnter) {
EditorActionUtil.moveCaretToLineEnd(editor, false, false);
}
formatInsertedString(context, stringToInsert.length());
break;
case _string:
case _integer:
insertPropertyWithEnum(context, editor, defaultValueAsString, values, finalType, comma);
insertPropertyWithEnum(context, editor, defaultValueAsString, values, finalType, comma, myWalker);
break;
default:
}
}
else {
insertPropertyWithEnum(context, editor, defaultValueAsString, values, null, comma);
insertPropertyWithEnum(context, editor, defaultValueAsString, values, null, comma, myWalker);
}
}
};
}
private static void invokeEnterHandler(Editor editor) {
EditorActionHandler handler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_ENTER);
handler.execute(editor, editor.getCaretModel().getCurrentCaret(),
DataManager.getInstance().getDataContext(editor.getContentComponent()));
}
private boolean handleInsideQuotesInsertion(@NotNull InsertionContext context, @NotNull Editor editor, boolean hasValue) {
if (myInsideStringLiteral) {
int offset = editor.getCaretModel().getOffset();
@@ -474,24 +530,30 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
}
}
public static void insertPropertyWithEnum(InsertionContext context,
private static void insertPropertyWithEnum(InsertionContext context,
Editor editor,
String defaultValue,
List<Object> values,
JsonSchemaType type, String comma) {
JsonSchemaType type,
String comma,
JsonLikePsiWalker walker) {
if (!walker.quotesForStringLiterals() && defaultValue != null) {
defaultValue = StringUtil.unquoteString(defaultValue);
}
final boolean isNumber = type != null && (JsonSchemaType._integer.equals(type) || JsonSchemaType._number.equals(type)) ||
type == null && (defaultValue != null &&
!StringUtil.isQuotedString(defaultValue) || values != null && ContainerUtil.and(values, v -> !(v instanceof String)));
boolean hasValues = !ContainerUtil.isEmpty(values);
boolean hasDefaultValue = !StringUtil.isEmpty(defaultValue);
String stringToInsert = ":" + (hasDefaultValue ? defaultValue : (isNumber ? "" : "\"\"")) + comma;
EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, false, true, 1);
if (!isNumber || hasDefaultValue) {
boolean hasQuotes = isNumber || !walker.quotesForStringLiterals();
String stringToInsert = ": " + (hasDefaultValue ? defaultValue : (hasQuotes ? "" : "\"\"")) + comma;
EditorModificationUtil.insertStringAtCaret(editor, stringToInsert, false, true, 2);
if (!hasQuotes || hasDefaultValue) {
SelectionModel model = editor.getSelectionModel();
int caretStart = model.getSelectionStart();
int newOffset = caretStart + (hasDefaultValue ? defaultValue.length() : 1);
if (hasDefaultValue && !isNumber) newOffset--;
model.setSelection(isNumber ? caretStart : (caretStart + 1), newOffset);
if (hasDefaultValue && !hasQuotes) newOffset--;
model.setSelection(hasQuotes ? caretStart : (caretStart + 1), newOffset);
editor.getCaretModel().moveToOffset(newOffset);
}
@@ -502,7 +564,8 @@ public class JsonSchemaCompletionContributor extends CompletionContributor {
}
}
public static void formatInsertedString(@NotNull InsertionContext context, int offset) {
public static void formatInsertedString(@NotNull InsertionContext context,
int offset) {
Project project = context.getProject();
PsiDocumentManager.getInstance(project).commitDocument(context.getDocument());
CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);

View File

@@ -4,6 +4,7 @@ package com.jetbrains.jsonSchema.impl;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.json.psi.JsonFile;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
@@ -64,7 +65,6 @@ public class JsonSchemaComplianceChecker {
rootToCheck = findTopLevelElement(myWalker, element);
} else {
rootToCheck = firstProp.getParentObject();
if (rootToCheck == null) rootToCheck = firstProp.getParentArray();
if (rootToCheck == null || !myWalker.isTopJsonElement(rootToCheck.getDelegate().getParent())) {
return;
}
@@ -81,7 +81,7 @@ public class JsonSchemaComplianceChecker {
if (checkIfAlreadyProcessed(entry.getKey())) continue;
String value = entry.getValue().getMessage();
if (myMessagePrefix != null) value = myMessagePrefix + value;
LocalQuickFix fix = entry.getValue().createFix();
LocalQuickFix fix = entry.getKey().getContainingFile() instanceof JsonFile ? entry.getValue().createFix() : null;
if (fix == null) {
myHolder.registerProblem(entry.getKey(), value);
}

View File

@@ -2,11 +2,8 @@
package com.jetbrains.jsonSchema.impl;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.json.JsonLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.AtomicClearableLazyValue;
import com.intellij.openapi.util.Factory;
@@ -281,9 +278,7 @@ public class JsonSchemaServiceImpl implements JsonSchemaService {
}
private static boolean isProviderAvailable(@NotNull final VirtualFile file, @NotNull JsonSchemaFileProvider provider) {
final FileType type = file.getFileType();
final boolean isJson = type instanceof LanguageFileType && ((LanguageFileType)type).getLanguage().isKindOf(JsonLanguage.INSTANCE);
return (isJson || !SchemaType.userSchema.equals(provider.getSchemaType())) && provider.isAvailable(file);
return provider.isAvailable(file);
}
@Nullable

View File

@@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable;
* @author Irina.Chernushina on 7/15/2015.
*/
public enum JsonSchemaType {
_string, _number, _integer, _object, _array, _boolean, _null, _any;
_string, _number, _integer, _object, _array, _boolean, _null, _any, _string_number;
public String getName() {
return name().substring(1);
@@ -20,6 +20,7 @@ public enum JsonSchemaType {
return "\"\"";
case _number:
case _integer:
case _string_number:
return "0";
case _object:
return "{}";
@@ -35,11 +36,29 @@ public enum JsonSchemaType {
}
}
public boolean isSimple() {
switch (this) {
case _string:
case _number:
case _integer:
case _boolean:
case _null:
return true;
case _object:
case _array:
case _any:
default:
return false;
}
}
@Nullable
static JsonSchemaType getType(@NotNull final JsonValueAdapter value) {
if (value.isNull()) return _null;
if (value.isBooleanLiteral()) return _boolean;
if (value.isStringLiteral()) return _string;
if (value.isStringLiteral()) {
return value.isNumberLiteral() ? _string_number : _string;
}
if (value.isArray()) return _array;
if (value.isObject()) return _object;
if (value.isNumberLiteral()) {

View File

@@ -20,7 +20,6 @@ import com.intellij.json.psi.JsonObject;
import com.intellij.json.psi.JsonProperty;
import com.intellij.json.psi.JsonValue;
import com.intellij.psi.PsiElement;
import com.jetbrains.jsonSchema.extension.adapters.JsonArrayValueAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonObjectValueAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter;
@@ -67,12 +66,6 @@ public class JsonJsonPropertyAdapter implements JsonPropertyAdapter {
return myProperty.getParent() instanceof JsonObject ? new JsonJsonObjectAdapter((JsonObject)myProperty.getParent()) : null;
}
@Nullable
@Override
public JsonArrayValueAdapter getParentArray() {
return myProperty.getParent() instanceof JsonArray ? new JsonJsonArrayAdapter((JsonArray)myProperty.getParent()) : null;
}
@NotNull
public static JsonValueAdapter createAdapterByType(@NotNull JsonValue value) {
if (value instanceof JsonObject) return new JsonJsonObjectAdapter((JsonObject)value);

View File

@@ -3,6 +3,7 @@ package com.jetbrains.jsonSchema.widget;
import com.intellij.icons.AllIcons;
import com.intellij.json.JsonLanguage;
import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.fileTypes.FileType;
@@ -34,7 +35,9 @@ import java.util.stream.Collectors;
class JsonSchemaStatusWidget extends EditorBasedStatusBarPopup {
private static final String JSON_SCHEMA_BAR = "JSON: ";
private static final String JSON_SCHEMA_BAR_OTHER_FILES = "Schema: ";
private static final String JSON_SCHEMA_TOOLTIP = "JSON Schema: ";
private static final String JSON_SCHEMA_TOOLTIP_OTHER_FILES = "Validated by JSON Schema: ";
private final JsonSchemaService myService;
private static final String ID = "JSONSchemaSelector";
@@ -146,16 +149,23 @@ class JsonSchemaStatusWidget extends EditorBasedStatusBarPopup {
return state;
}
FileType fileType = file.getFileType();
Language language = fileType instanceof LanguageFileType ? ((LanguageFileType)fileType).getLanguage() : null;
boolean isJsonFile = language instanceof JsonLanguage;
String tooltip = isJsonFile ? JSON_SCHEMA_TOOLTIP : JSON_SCHEMA_TOOLTIP_OTHER_FILES;
String bar = isJsonFile ? JSON_SCHEMA_BAR : JSON_SCHEMA_BAR_OTHER_FILES;
JsonSchemaFileProvider provider = myService.getSchemaProvider(schemaFile);
if (provider != null) {
String providerName = provider.getPresentableName();
String shortName = StringUtil.trimEnd(StringUtil.trimEnd(providerName, ".json"), "-schema");
String name = shortName.startsWith("JSON schema") ? shortName : (JSON_SCHEMA_BAR + shortName);
String name = shortName.startsWith("JSON schema") ? shortName : (bar + shortName);
String kind = provider.getSchemaType() == SchemaType.embeddedSchema || provider.getSchemaType() == SchemaType.schema ? " (bundled)" : "";
return new MyWidgetState(JSON_SCHEMA_TOOLTIP + providerName + kind, name, true);
return new MyWidgetState(tooltip + providerName + kind, name, true);
}
return new MyWidgetState(JSON_SCHEMA_TOOLTIP + getSchemaFileDesc(schemaFile), JSON_SCHEMA_BAR + getPresentableNameForFile(schemaFile),
return new MyWidgetState(tooltip + getSchemaFileDesc(schemaFile), bar + getPresentableNameForFile(schemaFile),
true);
}

View File

@@ -1,36 +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;
import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.extensions.AreaPicoContainer;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.json.JsonLanguage;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.PlatformTestUtil;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.intellij.util.containers.Predicate;
import com.jetbrains.jsonSchema.impl.JsonSchemaComplianceInspection;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @author Irina.Chernushina on 9/21/2015.
*/
public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
public class JsonSchemaHighlightingTest extends JsonSchemaHighlightingTestBase {
@Override
protected String getTestDataPath() {
return PlatformTestUtil.getCommunityPath() + "/json/tests/testData/jsonSchema/highlighting";
}
@Override
protected String getTestFileName() {
return "config.json";
}
@Override
protected InspectionProfileEntry getInspectionProfile() {
return new JsonSchemaComplianceInspection();
}
@Override
protected Predicate<VirtualFile> getAvailabilityPredicate() {
return file -> file.getFileType() instanceof LanguageFileType && ((LanguageFileType)file.getFileType()).getLanguage().isKindOf(
JsonLanguage.INSTANCE);
}
public void testNumberMultipleWrong() throws Exception {
doTest("{ \"properties\": { \"prop\": {\"type\": \"number\", \"multipleOf\": 2}}}",
"{ \"prop\": <warning descr=\"Is not multiple of 2\">3</warning>}");
@@ -644,36 +654,6 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase {
return "{\"type\": \"object\", \"properties\": {\"prop\": " + s + "}}";
}
private void doTest(@Language("JSON") @NotNull final String schema, @NotNull final String text) throws Exception {
enableInspectionTool(new JsonSchemaComplianceInspection());
final PsiFile file = createFile(myModule, "config.json", text);
registerProvider(getProject(), schema);
Disposer.register(getTestRootDisposable(), new Disposable() {
@Override
public void dispose() {
JsonSchemaTestServiceImpl.setProvider(null);
}
});
configureByFile(file.getVirtualFile());
doTest(file.getVirtualFile(), true, false);
}
public void registerProvider(Project project, @NotNull String schema) throws IOException {
File dir = createTempDir("json_schema_test", true);
File child = new File(dir, "schema.json");
//noinspection ResultOfMethodCallIgnored
child.createNewFile();
FileUtil.writeToFile(child, schema);
VirtualFile schemaFile = getVirtualFile(child);
JsonSchemaTestServiceImpl.setProvider(new JsonSchemaTestProvider(schemaFile));
AreaPicoContainer container = Extensions.getArea(project).getPicoContainer();
String key = JsonSchemaService.class.getName();
container.unregisterComponent(key);
container.registerComponentImplementation(key, JsonSchemaTestServiceImpl.class);
}
public void testExclusiveMinMaxV6() throws Exception {
@Language("JSON") String exclusiveMinSchema = "{\"properties\": {\"prop\": {\"exclusiveMinimum\": 3}}}";
doTest(exclusiveMinSchema, "{\"prop\": <warning>2</warning>}");

View File

@@ -0,0 +1,57 @@
// 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;
import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.extensions.AreaPicoContainer;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.util.containers.Predicate;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
public abstract class JsonSchemaHighlightingTestBase extends DaemonAnalyzerTestCase {
protected abstract String getTestFileName();
protected abstract InspectionProfileEntry getInspectionProfile();
protected abstract Predicate<VirtualFile> getAvailabilityPredicate();
protected void doTest(@Language("JSON") @NotNull final String schema, @NotNull final String text) throws Exception {
enableInspectionTool(getInspectionProfile());
final PsiFile file = createFile(myModule, getTestFileName(), text);
registerProvider(getProject(), schema);
Disposer.register(getTestRootDisposable(), new Disposable() {
@Override
public void dispose() {
JsonSchemaTestServiceImpl.setProvider(null);
}
});
configureByFile(file.getVirtualFile());
doTest(file.getVirtualFile(), true, false);
}
private void registerProvider(Project project, @NotNull String schema) throws IOException {
File dir = createTempDir("json_schema_test", true);
File child = new File(dir, "schema.json");
//noinspection ResultOfMethodCallIgnored
child.createNewFile();
FileUtil.writeToFile(child, schema);
VirtualFile schemaFile = getVirtualFile(child);
JsonSchemaTestServiceImpl.setProvider(new JsonSchemaTestProvider(schemaFile, getAvailabilityPredicate()));
AreaPicoContainer container = Extensions.getArea(project).getPicoContainer();
String key = JsonSchemaService.class.getName();
container.unregisterComponent(key);
container.registerComponentImplementation(key, JsonSchemaTestServiceImpl.class);
}
}

View File

@@ -1,9 +1,8 @@
package com.jetbrains.jsonSchema;
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.extension.JsonSchemaFileProvider;
import com.jetbrains.jsonSchema.extension.SchemaType;
import org.jetbrains.annotations.NotNull;
@@ -11,14 +10,16 @@ import org.jetbrains.annotations.Nullable;
public class JsonSchemaTestProvider implements JsonSchemaFileProvider {
private final VirtualFile mySchemaFile;
private final Predicate<? super VirtualFile> myAvailabilityPredicate;
public JsonSchemaTestProvider(VirtualFile schemaFile) {
public JsonSchemaTestProvider(VirtualFile schemaFile, Predicate<? super VirtualFile> availabilityPredicate) {
mySchemaFile = schemaFile;
myAvailabilityPredicate = availabilityPredicate;
}
@Override
public boolean isAvailable(@NotNull VirtualFile file) {
return file.getFileType() instanceof LanguageFileType && ((LanguageFileType)file.getFileType()).getLanguage().isKindOf(JsonLanguage.INSTANCE);
return myAvailabilityPredicate.apply(file);
}
@NotNull

View File

@@ -20,6 +20,7 @@ import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.EditorTestUtil;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
@@ -31,7 +32,7 @@ import java.util.List;
* @author Irina.Chernushina on 2/20/2017.
*/
public abstract class JsonBySchemaCompletionBaseTest extends CompletionTestCase {
protected void testBySchema(@NotNull final String schema, final @NotNull String text, final @NotNull String extension,
protected void testBySchema(@Language("JSON") @NotNull final String schema, final @NotNull String text, final @NotNull String extension,
final @NotNull String... variants) throws Exception {
final int position = EditorTestUtil.getCaretPosition(text);
Assert.assertTrue(position > 0);

View File

@@ -1,8 +1,6 @@
// 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.codeInsight.completion.CodeCompletionHandlerBase;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.json.JsonFileType;
import com.intellij.json.psi.JsonFile;
import com.intellij.json.psi.JsonObject;
@@ -13,17 +11,17 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.jsonSchema.JsonSchemaHeavyAbstractTest;
import com.jetbrains.jsonSchema.UserDefinedJsonSchemaConfiguration;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.Collections;
/**
* @author Irina.Chernushina on 3/4/2017.
*/
public class JsonBySchemaHeavyCompletionTest extends JsonSchemaHeavyAbstractTest {
public class JsonBySchemaHeavyCompletionTest extends JsonBySchemaHeavyCompletionTestBase {
@Override
protected String getExtensionWithoutDot() {
return "json";
}
@Override
protected String getBasePath() {
return "/tests/testData/jsonSchema/completion";
@@ -61,6 +59,10 @@ public class JsonBySchemaHeavyCompletionTest extends JsonSchemaHeavyAbstractTest
baseInsertTest("insertPropertyName", "testObjectType");
}
public void testInsertArrayType() throws Exception {
baseInsertTest("insertPropertyName", "testArrayType");
}
public void testInsertBooleanType() throws Exception {
baseInsertTest("insertPropertyName", "testBooleanType");
}
@@ -133,52 +135,5 @@ public class JsonBySchemaHeavyCompletionTest extends JsonSchemaHeavyAbstractTest
});
}
private void baseCompletionTest(@SuppressWarnings("SameParameterValue") final String folder,
@SuppressWarnings("SameParameterValue") final String testFile, @NotNull String... items) throws Exception {
baseTest(folder, testFile, () -> {
complete();
assertStringItems(items);
});
}
private void baseInsertTest(@SuppressWarnings("SameParameterValue") final String folder, final String testFile) throws Exception {
baseTest(folder, testFile, () -> {
final CodeCompletionHandlerBase handlerBase = new CodeCompletionHandlerBase(CompletionType.BASIC);
handlerBase.invokeCompletion(getProject(), getEditor());
if (myItems != null) {
selectItem(myItems[0]);
}
try {
checkResultByFile("/" + folder + "/" + testFile + "_after.json");
}
catch (Exception e) {
throw new RuntimeException(e);
}
});
}
private void baseTest(@NotNull final String folder, @NotNull final String testFile, @NotNull final Runnable checker) throws Exception {
skeleton(new Callback() {
@Override
public void registerSchemes() {
final String moduleDir = getModuleDir(getProject());
final UserDefinedJsonSchemaConfiguration base =
new UserDefinedJsonSchemaConfiguration("base", JsonSchemaVersion.SCHEMA_4, moduleDir + "/Schema.json", false,
Collections.singletonList(new UserDefinedJsonSchemaConfiguration.Item(testFile + ".json", true, false))
);
addSchema(base);
}
@Override
public void configureFiles() {
configureByFiles(null, "/" + folder + "/" + testFile + ".json", "/" + folder + "/Schema.json");
}
@Override
public void doCheck() {
checker.run();
}
});
}
}

View File

@@ -0,0 +1,64 @@
// 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.codeInsight.completion.CodeCompletionHandlerBase;
import com.intellij.codeInsight.completion.CompletionType;
import com.jetbrains.jsonSchema.JsonSchemaHeavyAbstractTest;
import com.jetbrains.jsonSchema.UserDefinedJsonSchemaConfiguration;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
public abstract class JsonBySchemaHeavyCompletionTestBase extends JsonSchemaHeavyAbstractTest {
protected void baseCompletionTest(@SuppressWarnings("SameParameterValue") final String folder,
@SuppressWarnings("SameParameterValue") final String testFile, @NotNull String... items) throws Exception {
baseTest(folder, testFile, () -> {
complete();
assertStringItems(items);
});
}
protected void baseInsertTest(@SuppressWarnings("SameParameterValue") final String folder, final String testFile) throws Exception {
baseTest(folder, testFile, () -> {
final CodeCompletionHandlerBase handlerBase = new CodeCompletionHandlerBase(CompletionType.BASIC);
handlerBase.invokeCompletion(getProject(), getEditor());
if (myItems != null) {
selectItem(myItems[0]);
}
try {
checkResultByFile("/" + folder + "/" + testFile + "_after." + getExtensionWithoutDot());
}
catch (Exception e) {
throw new RuntimeException(e);
}
});
}
protected abstract String getExtensionWithoutDot();
protected void baseTest(@NotNull final String folder, @NotNull final String testFile, @NotNull final Runnable checker) throws Exception {
skeleton(new JsonSchemaHeavyAbstractTest.Callback() {
@Override
public void registerSchemes() {
final String moduleDir = getModuleDir(getProject());
final UserDefinedJsonSchemaConfiguration base =
new UserDefinedJsonSchemaConfiguration("base", JsonSchemaVersion.SCHEMA_4, moduleDir + "/Schema.json", false,
Collections
.singletonList(new UserDefinedJsonSchemaConfiguration.Item(testFile + "." + getExtensionWithoutDot(), true, false))
);
addSchema(base);
}
@Override
public void configureFiles() {
configureByFiles(null, "/" + folder + "/" + testFile + "." + getExtensionWithoutDot(), "/" + folder + "/Schema.json");
}
@Override
public void doCheck() {
checker.run();
}
});
}
}

View File

@@ -20,6 +20,9 @@
},
"booleanType": {
"type": "boolean"
},
"arrayType": {
"type": "array"
}
}
}
}

View File

@@ -0,0 +1,3 @@
{
"arrayTy<caret>"
}

View File

@@ -0,0 +1,3 @@
{
"arrayType": [<caret>]
}

View File

@@ -1,3 +1,3 @@
{
"integerType":<caret>
"integerType": <caret>
}

View File

@@ -16,5 +16,8 @@
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.spellchecker" />
<orderEntry type="module" module-name="intellij.xml" />
<orderEntry type="module" module-name="intellij.json" />
<orderEntry type="module" module-name="intellij.json.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.testFramework" scope="TEST" />
</component>
</module>

View File

@@ -26,6 +26,10 @@
<projectService serviceImplementation="org.jetbrains.yaml.YAMLElementGenerator"/>
<completion.contributor order="first" language="yaml"
implementationClass="org.jetbrains.yaml.schema.YamlJsonSchemaCompletionContributor"/>
<lang.documentationProvider language="yaml" implementationClass="org.jetbrains.yaml.schema.YamlJsonSchemaDocumentationProvider" order="first"/>
<lang.elementManipulator forClass="org.jetbrains.yaml.psi.impl.YAMLScalarImpl"
implementationClass="org.jetbrains.yaml.psi.impl.YAMLScalarElementManipulator"/>
<breadcrumbsInfoProvider implementation="org.jetbrains.yaml.breadcrumbs.YAMLBreadcrumbsInfoProvider"/>
@@ -55,8 +59,20 @@
groupKey="inspections.group.name" enabledByDefault="true"
implementationClass="org.jetbrains.yaml.inspections.YAMLUnusedAnchorInspection"/>
<localInspection language="yaml" bundle="messages.YAMLBundle"
shortName="YAMLSchemaValidation"
key="inspections.schema.validation.name"
groupKey="inspections.group.name"
enabledByDefault="true"
level="WARNING"
implementationClass="org.jetbrains.yaml.schema.YamlJsonSchemaHighlightingInspection"/>
</extensions>
<extensions defaultExtensionNs="Json">
<Like.Psi.Walker.Factory implementation="org.jetbrains.yaml.schema.YamlJsonLikePsiWalkerFactory"/>
<Schema.Enabler implementation="org.jetbrains.yaml.schema.YamlJsonEnabler" />
</extensions>
<extensions defaultExtensionNs="com.intellij.spellchecker">
<support language="yaml" implementationClass="org.jetbrains.yaml.YAMLSpellcheckerStrategy"/>
</extensions>

View File

@@ -0,0 +1,6 @@
<html>
<body>
Validates YAML file against a JSON Schema, if the schema is specified
<!-- tooltip end -->
</body>
</html>

View File

@@ -69,6 +69,7 @@ inspections.unresolved.alias.name=Unresolved alias
inspections.unresolved.alias.message=Can''t resolve alias {0}
inspections.recursive.alias.name=Recursive alias
inspections.recursive.alias.message=Alias can't be recursive
inspections.schema.validation.name=Validation by JSON Schema
inspections.duplicated.keys.name=Duplicated YAML keys
inspections.unused.anchor.name=Unused anchor
inspections.unused.anchor.message=Anchor {0} is never used

View File

@@ -0,0 +1,96 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.jsonSchema.extension.adapters.JsonArrayValueAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonObjectValueAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.psi.YAMLSequence;
import org.jetbrains.yaml.psi.YAMLSequenceItem;
import org.jetbrains.yaml.psi.YAMLValue;
import java.util.List;
public class YamlArrayAdapter implements JsonArrayValueAdapter {
@NotNull private final YAMLSequence myArray;
@NotNull private final NotNullLazyValue<List<JsonValueAdapter>> myChildAdapters = new NotNullLazyValue<List<JsonValueAdapter>>() {
@NotNull
@Override
protected List<JsonValueAdapter> compute() {
return computeChildAdapters();
}
};
public YamlArrayAdapter(@NotNull YAMLSequence array) {myArray = array;}
@NotNull
@Override
public List<JsonValueAdapter> getElements() {
return myChildAdapters.getValue();
}
@Override
public boolean isObject() {
return false;
}
@Override
public boolean isArray() {
return true;
}
@Override
public boolean isStringLiteral() {
return false;
}
@Override
public boolean isNumberLiteral() {
return false;
}
@Override
public boolean isBooleanLiteral() {
return false;
}
@NotNull
@Override
public PsiElement getDelegate() {
return myArray;
}
@Nullable
@Override
public JsonObjectValueAdapter getAsObject() {
return null;
}
@Nullable
@Override
public JsonArrayValueAdapter getAsArray() {
return this;
}
@Override
public boolean shouldCheckIntegralRequirements() {
return true;
}
@NotNull
private List<JsonValueAdapter> computeChildAdapters() {
List<YAMLSequenceItem> items = myArray.getItems();
List<JsonValueAdapter> adapters = ContainerUtil.newArrayListWithCapacity(items.size());
for (YAMLSequenceItem item: items) {
YAMLValue value = item.getValue();
if (value == null) continue;
adapters.add(YamlPropertyAdapter.createValueAdapterByType(value));
}
return adapters;
}
}

View File

@@ -0,0 +1,117 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.jetbrains.jsonSchema.extension.adapters.JsonArrayValueAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonObjectValueAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.psi.YAMLValue;
import java.util.regex.Pattern;
public class YamlGenericValueAdapter implements JsonValueAdapter {
@NotNull private final YAMLValue myValue;
public YamlGenericValueAdapter(@NotNull YAMLValue value) {myValue = value;}
@Override
public boolean isShouldBeIgnored() {
return true;
}
@Override
public boolean isObject() {
return false;
}
@Override
public boolean isArray() {
return false;
}
@Override
public boolean isStringLiteral() {
String text = myValue.getText();
return !hasNonStringTags(text); /*values should always validate as string*/
}
private static boolean hasNonStringTags(@NotNull String text) {
return hasTag(text, "bool")
|| hasTag(text, "null")
|| hasTag(text, "int")
|| hasTag(text, "float");
}
private static boolean hasTag(@NotNull String text, @NotNull String tagName) {
return StringUtil.startsWith(text, "!!" + tagName);
}
@Override
public boolean isNumberLiteral() {
String text = myValue.getText();
return isNumber(text);
}
@Override
public boolean isBooleanLiteral() {
String text = myValue.getText();
return "true".equals(text) || "false".equals(text) || hasTag(text, "bool");
}
@Override
public boolean isNull() {
String text = myValue.getText();
return "null".equals(text) || hasTag(text, "null");
}
@NotNull
@Override
public PsiElement getDelegate() {
return myValue;
}
@Nullable
@Override
public JsonObjectValueAdapter getAsObject() {
return null;
}
@Nullable
@Override
public JsonArrayValueAdapter getAsArray() {
return null;
}
@Override
public boolean shouldCheckIntegralRequirements() {
return false;
}
private static boolean isNumber(@Nullable String s) {
if (s == null) return false;
return isInteger(s) || isFloat(s);
}
// http://yaml.org/spec/1.2/spec.html#id2803828
private static boolean isInteger(@NotNull String s) {
if (s.length() == 0) return false;
if ("0".equals(s)) return true;
if (hasTag(s, "int")) return true;
int startIndex = s.charAt(0) == '-' ? 1 : 0;
for (int i = startIndex; i < s.length(); ++i) {
if (i == startIndex && s.charAt(i) == '0') return false;
if (!Character.isDigit(s.charAt(i))) return false;
}
return true;
}
// http://yaml.org/spec/1.2/spec.html#id2804092
private static boolean isFloat(@NotNull String s) {
if (".inf".equals(s) || "-.inf".equals(s) || ".nan".equals(s)) return true;
if (hasTag(s, "float")) return true;
return Pattern.matches("-?[1-9](\\.[0-9]*[1-9])?(e[-+][1-9][0-9]*)?", s);
}
}

View File

@@ -0,0 +1,16 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.jsonSchema.extension.JsonSchemaEnabler;
import org.jetbrains.yaml.YAMLLanguage;
public class YamlJsonEnabler implements JsonSchemaEnabler {
@Override
public boolean isEnabledForFile(VirtualFile file) {
FileType type = file.getFileType();
return type instanceof LanguageFileType && ((LanguageFileType)type).getLanguage() instanceof YAMLLanguage;
}
}

View File

@@ -0,0 +1,22 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.psi.PsiElement;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalkerFactory;
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.yaml.psi.YAMLFile;
public class YamlJsonLikePsiWalkerFactory implements JsonLikePsiWalkerFactory {
@Override
public boolean handles(@NotNull PsiElement element) {
return element.getContainingFile() instanceof YAMLFile;
}
@NotNull
@Override
public JsonLikePsiWalker create(@NotNull JsonSchemaObject schemaObject) {
return new YamlJsonPsiWalker();
}
}

View File

@@ -0,0 +1,205 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ThreeState;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter;
import com.jetbrains.jsonSchema.impl.JsonSchemaVariantsTreeBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.YAMLTokenTypes;
import org.jetbrains.yaml.psi.*;
import org.jetbrains.yaml.psi.impl.YAMLBlockMappingImpl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class YamlJsonPsiWalker implements JsonLikePsiWalker {
public YamlJsonPsiWalker() {
}
@Override
public ThreeState isName(PsiElement element) {
PsiElement parent = element.getParent();
if (parent instanceof YAMLDocument || parent instanceof YAMLMapping) {
return ThreeState.YES;
}
if (parent instanceof YAMLKeyValue && isFirstChild(element, parent)) {
ASTNode prev = element.getNode().getTreePrev();
return prev.getElementType() == YAMLTokenTypes.INDENT ? ThreeState.YES : ThreeState.NO;
}
if (parent instanceof YAMLSequenceItem && isFirstChild(element, parent)) {
return ThreeState.UNSURE;
}
return ThreeState.NO;
}
private static boolean isFirstChild(PsiElement element, PsiElement parent) {
PsiElement[] children = parent.getChildren();
return children.length != 0 && children[0] == element;
}
@Override
public boolean isPropertyWithValue(@NotNull PsiElement element) {
return element instanceof YAMLKeyValue && ((YAMLKeyValue)element).getValue() != null;
}
@Override
public boolean isTopJsonElement(@NotNull PsiElement element) {
return element instanceof YAMLFile || element instanceof YAMLDocument;
}
@Override
public PsiElement goUpToCheckable(@NotNull PsiElement element) {
PsiElement current = element;
while (current != null && !(current instanceof PsiFile)) {
if (current instanceof YAMLValue || current instanceof YAMLKeyValue) {
return current;
}
current = current.getParent();
}
return null;
}
@Override
public boolean isNameQuoted() {
return false;
}
@Nullable
@Override
public JsonValueAdapter createValueAdapter(@NotNull PsiElement element) {
return element instanceof YAMLValue ? YamlPropertyAdapter.createValueAdapterByType((YAMLValue)element) : null;
}
@Override
public boolean onlyDoubleQuotesForStringLiterals() {
return false;
}
@Override
public boolean hasPropertiesBehindAndNoComma(@NotNull PsiElement element) {
return false;
}
@Nullable
@Override
public JsonPropertyAdapter getParentPropertyAdapter(@NotNull PsiElement element) {
final YAMLKeyValue property = PsiTreeUtil.getParentOfType(element, YAMLKeyValue.class, false);
if (property == null) return null;
// it is a parent property only if its value contains the current property
YAMLValue value = property.getValue();
if (value == null || !PsiTreeUtil.isAncestor(value, element, true)) return null;
return new YamlPropertyAdapter(property);
}
@Override
public Set<String> getPropertyNamesOfParentObject(@NotNull PsiElement originalPosition, PsiElement computedPosition) {
YAMLMapping object = PsiTreeUtil.getParentOfType(originalPosition, YAMLMapping.class);
if (object == null) object = PsiTreeUtil.getParentOfType(computedPosition, YAMLMapping.class);
if (object == null) return Collections.emptySet();
return object.getKeyValues().stream().filter(p -> p != null && p.getName() != null)
.map(p -> p.getName()).collect(Collectors.toSet());
}
@Nullable
@Override
public List<JsonSchemaVariantsTreeBuilder.Step> findPosition(@NotNull PsiElement element, boolean forceLastTransition) {
final List<JsonSchemaVariantsTreeBuilder.Step> steps = new ArrayList<>();
PsiElement current = element;
while (!breakCondition(current)) {
final PsiElement position = current;
current = current.getParent();
if (current instanceof YAMLSequence) {
YAMLSequence array = (YAMLSequence)current;
final List<YAMLSequenceItem> expressions = array.getItems();
int idx = -1;
for (int i = 0; i < expressions.size(); i++) {
final YAMLSequenceItem value = expressions.get(i);
if (position.equals(value)) {
idx = i;
break;
}
}
if (idx != -1) {
steps.add(JsonSchemaVariantsTreeBuilder.Step.createArrayElementStep(idx));
}
} else if (current instanceof YAMLSequenceItem) {
// do nothing - handled by the upper condition
} else if (current instanceof YAMLKeyValue) {
final String propertyName = StringUtil.notNullize(((YAMLKeyValue)current).getName());
current = current.getParent();
if (!(current instanceof YAMLMapping)) return null;//incorrect syntax?
steps.add(JsonSchemaVariantsTreeBuilder.Step.createPropertyStep(propertyName));
} else if (current instanceof YAMLMapping && position instanceof YAMLKeyValue) {
// if either value or not first in the chain - needed for completion variant
final String propertyName = StringUtil.notNullize(((YAMLKeyValue)position).getName());
steps.add(JsonSchemaVariantsTreeBuilder.Step.createPropertyStep(propertyName));
} else if (breakCondition(current)) {
break;
} else {
if (current instanceof YAMLMapping) {
List<YAMLPsiElement> elements = ((YAMLMapping)current).getYAMLElements();
if (elements.size() == 0) return null;
YAMLPsiElement last = elements.get(elements.size() - 1);
if (last == position) {
continue;
}
}
return null;//something went wrong
}
}
Collections.reverse(steps);
return steps;
}
private static boolean breakCondition(PsiElement current) {
return current instanceof PsiFile || current instanceof YAMLDocument ||
current instanceof YAMLBlockMappingImpl && current.getParent() instanceof YAMLDocument;
}
@Override
public boolean quotesForStringLiterals() {
return false;
}
@Override
public String getDefaultObjectValue(boolean includeWhitespaces) {
return includeWhitespaces ? "\n " : "";
}
@Nullable public String defaultObjectValueDescription() { return "start object"; }
@Override
public String getDefaultArrayValue(boolean includeWhitespaces) {
return includeWhitespaces ? "\n - " : "- ";
}
@Nullable public String defaultArrayValueDescription() { return "start array"; }
@Override
public boolean invokeEnterBeforeObjectAndArray() {
return true;
}
@Override
public String getNodeTextForValidation(PsiElement element) {
String text = element.getText();
if (!StringUtil.startsWith(text, "!!")) return text;
// remove tags
int spaceIndex = text.indexOf(' ');
return spaceIndex > 0 ? text.substring(spaceIndex + 1) : text;
}
}

View File

@@ -0,0 +1,23 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.psi.PsiElement;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.jetbrains.jsonSchema.impl.JsonSchemaCompletionContributor;
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
import org.jetbrains.annotations.NotNull;
public class YamlJsonSchemaCompletionContributor extends CompletionContributor {
@Override
public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) {
final PsiElement position = parameters.getPosition();
final JsonSchemaService jsonSchemaService = JsonSchemaService.Impl.get(position.getProject());
JsonSchemaObject object = jsonSchemaService.getSchemaObject(parameters.getOriginalFile().getVirtualFile());
if (object != null) {
JsonSchemaCompletionContributor.doCompletion(parameters, result, object);
}
}
}

View File

@@ -0,0 +1,68 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.lang.documentation.DocumentationProviderEx;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.jetbrains.jsonSchema.impl.JsonSchemaDocumentationProvider;
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class YamlJsonSchemaDocumentationProvider extends DocumentationProviderEx {
@Nullable
@Override
public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
return findSchemaAndGenerateDoc(element, true);
}
@Nullable
@Override
public List<String> getUrlFor(PsiElement element, PsiElement originalElement) {
return null;
}
@Nullable
@Override
public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
return findSchemaAndGenerateDoc(element, false);
}
@Nullable
private static String findSchemaAndGenerateDoc(PsiElement element, final boolean preferShort) {
final JsonSchemaService jsonSchemaService = JsonSchemaService.Impl.get(element.getProject());
PsiFile containingFile = element.getContainingFile();
if (containingFile == null) return null;
VirtualFile virtualFile = containingFile.getVirtualFile();
if (virtualFile == null) return null;
JsonSchemaObject schemaObject = jsonSchemaService.getSchemaObject(virtualFile);
if (schemaObject == null) return null;
return JsonSchemaDocumentationProvider.generateDoc(element, schemaObject, preferShort);
}
@Nullable
@Override
public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) {
return null;
}
@Nullable
@Override
public PsiElement getDocumentationElementForLink(PsiManager psiManager, String link, PsiElement context) {
return null;
}
@Nullable
@Override
public PsiElement getCustomDocumentationElement(@NotNull Editor editor,
@NotNull PsiFile file,
@Nullable PsiElement contextElement) {
return contextElement;
}
}

View File

@@ -0,0 +1,65 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.jetbrains.jsonSchema.impl.JsonSchemaComplianceChecker;
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.yaml.YAMLBundle;
import org.jetbrains.yaml.psi.YAMLDocument;
import org.jetbrains.yaml.psi.YAMLFile;
import org.jetbrains.yaml.psi.YamlPsiElementVisitor;
import java.util.List;
public class YamlJsonSchemaHighlightingInspection extends LocalInspectionTool {
@Nls
@NotNull
@Override
public String getDisplayName() {
return YAMLBundle.message("inspections.schema.validation.name");
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session) {
PsiFile file = holder.getFile();
if (!(file instanceof YAMLFile)) return PsiElementVisitor.EMPTY_VISITOR;
List<YAMLDocument> documents = ((YAMLFile)file).getDocuments();
if (documents.size() != 1) return PsiElementVisitor.EMPTY_VISITOR;
PsiElement root = documents.get(0).getTopLevelValue();
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;
JsonSchemaObject object = service.getSchemaObject(virtualFile);
if (object == null) return PsiElementVisitor.EMPTY_VISITOR;
return new YamlPsiElementVisitor() {
@Override
public void visitElement(PsiElement element) {
if (element != root) return;
final JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(element, object);
if (walker == null) return;
new JsonSchemaComplianceChecker(object, holder, walker, session, "Schema validation: ").annotate(element);
}
};
}
}

View File

@@ -0,0 +1,94 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.jsonSchema.extension.adapters.JsonArrayValueAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonObjectValueAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.psi.YAMLKeyValue;
import org.jetbrains.yaml.psi.YAMLMapping;
import java.util.Collection;
import java.util.List;
public class YamlObjectAdapter implements JsonObjectValueAdapter {
@NotNull private final YAMLMapping myObject;
@NotNull private final NotNullLazyValue<List<JsonPropertyAdapter>> myChildAdapters = new NotNullLazyValue<List<JsonPropertyAdapter>>() {
@NotNull
@Override
protected List<JsonPropertyAdapter> compute() {
return computeChildAdapters();
}
};
public YamlObjectAdapter(@NotNull YAMLMapping object) {myObject = object;}
@Override
public boolean isObject() {
return true;
}
@Override
public boolean isArray() {
return false;
}
@Override
public boolean isStringLiteral() {
return false;
}
@Override
public boolean isNumberLiteral() {
return false;
}
@Override
public boolean isBooleanLiteral() {
return false;
}
@NotNull
@Override
public PsiElement getDelegate() {
return myObject;
}
@Nullable
@Override
public JsonObjectValueAdapter getAsObject() {
return this;
}
@Nullable
@Override
public JsonArrayValueAdapter getAsArray() {
return null;
}
@Override
public boolean shouldCheckIntegralRequirements() {
return true;
}
@NotNull
@Override
public List<JsonPropertyAdapter> getPropertyList() {
return myChildAdapters.getValue();
}
@NotNull
private List<JsonPropertyAdapter> computeChildAdapters() {
Collection<YAMLKeyValue> keyValues = myObject.getKeyValues();
List<JsonPropertyAdapter> adapters = ContainerUtil.newArrayListWithCapacity(keyValues.size());
for (YAMLKeyValue value : keyValues) {
adapters.add(new YamlPropertyAdapter(value));
}
return adapters;
}
}

View File

@@ -0,0 +1,58 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.psi.PsiElement;
import com.jetbrains.jsonSchema.extension.adapters.JsonObjectValueAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.psi.YAMLKeyValue;
import org.jetbrains.yaml.psi.YAMLMapping;
import org.jetbrains.yaml.psi.YAMLSequence;
import org.jetbrains.yaml.psi.YAMLValue;
public class YamlPropertyAdapter implements JsonPropertyAdapter {
private final YAMLKeyValue myProperty;
public YamlPropertyAdapter(@NotNull YAMLKeyValue property) {myProperty = property;}
@Nullable
@Override
public String getName() {
return myProperty.getKeyText();
}
@Nullable
@Override
public JsonValueAdapter getNameValueAdapter() {
return null; // todo: we need a separate adapter for names; but currently names schema is rarely used, let's just skip validation
}
@Nullable
@Override
public JsonValueAdapter getValue() {
YAMLValue value = myProperty.getValue();
return value == null ? null : createValueAdapterByType(value);
}
@NotNull
@Override
public PsiElement getDelegate() {
return myProperty;
}
@Nullable
@Override
public JsonObjectValueAdapter getParentObject() {
return myProperty.getParentMapping() != null ? new YamlObjectAdapter(myProperty.getParentMapping()) : null;
}
@NotNull
public static JsonValueAdapter createValueAdapterByType(@NotNull YAMLValue value) {
if (value instanceof YAMLMapping) return new YamlObjectAdapter((YAMLMapping) value);
if (value instanceof YAMLSequence) return new YamlArrayAdapter((YAMLSequence) value);
return new YamlGenericValueAdapter(value);
}
}

View File

@@ -0,0 +1,53 @@
// 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 org.jetbrains.yaml.schema;
import com.jetbrains.jsonSchema.impl.JsonBySchemaCompletionBaseTest;
public class YamlByJsonSchemaCompletionTest extends JsonBySchemaCompletionBaseTest {
public void testTopLevel() throws Exception {
testBySchema("{\"properties\": {\"prima\": {}, \"proto\": {}, \"primus\": {}}}", "proto: 5\n<caret>", "yml",
"prima", "primus");
}
public void testNested() throws Exception {
testBySchema("{\"properties\": {\"prima\": {\"properties\": {\"proto\": {}, \"primus\": {}}}}}",
"prima:\n <caret>", "yml",
"primus", "proto");
}
public void testNestedInArray() throws Exception {
testBySchema("{\n" +
" \"properties\": {\n" +
" \"colorMap\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"properties\": {\n" +
" \"hue\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"saturation\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"value\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}", "colorMap:\n - <caret>", "yml", "hue", "saturation", "value");
}
public void testEnumInArray() throws Exception {
testBySchema("{\n" +
" \"properties\": {\n" +
" \"colorMap\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"enum\": [\"white\", \"blue\", \"red\"]\n" +
" }\n" +
" }\n" +
" }\n" +
"}", "colorMap:\n - <caret>", "yml", "blue", "red", "white");
}
}

View File

@@ -0,0 +1,21 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.jetbrains.jsonSchema.JsonBySchemaDocumentationBaseTest;
public class YamlByJsonSchemaDocumentationTest extends JsonBySchemaDocumentationBaseTest {
@Override
public String getTestDataPath() {
return PathManagerEx.getCommunityHomePath() + "/plugins/yaml/testSrc/org/jetbrains/yaml/schema/data";
}
@Override
protected String getBasePath() {
return ""; // unused
}
public void testDoc() throws Exception {
doTest(true, "yml");
}
}

View File

@@ -0,0 +1,71 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.json.JsonFileType;
import com.intellij.json.psi.JsonFile;
import com.intellij.json.psi.JsonObject;
import com.intellij.json.psi.JsonStringLiteral;
import com.intellij.json.psi.JsonValue;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.jsonSchema.impl.JsonBySchemaHeavyCompletionTest;
import org.junit.Assert;
public class YamlByJsonSchemaHeavyCompletionTest extends JsonBySchemaHeavyCompletionTest {
@Override
public String getTestDataPath() {
return PathManagerEx.getCommunityHomePath() + "/plugins/yaml/testSrc/org/jetbrains/yaml/schema/data/completion";
}
@Override
protected String getBasePath() {
return ""; // unused
}
@Override
protected String getExtensionWithoutDot() {
return "yml";
}
@Override
public void testEditingSchemaAffectsCompletion() throws Exception {
baseTest(getTestName(true), "testEditing", () -> {
complete();
assertStringItems("preserve", "react", "react-native");
final PsiFile schema = myFile.getParent().findFile("Schema.json");
final int idx = schema.getText().indexOf("react-native");
Assert.assertTrue(idx > 0);
PsiElement element = schema.findElementAt(idx);
element = element instanceof JsonStringLiteral ? element : PsiTreeUtil.getParentOfType(element, JsonStringLiteral.class);
Assert.assertTrue(element instanceof JsonStringLiteral);
final PsiFile dummy = PsiFileFactory.getInstance(myProject).createFileFromText("test.json", JsonFileType.INSTANCE,
"{\"a\": \"completelyChanged\"}");
Assert.assertTrue(dummy instanceof JsonFile);
final JsonValue top = ((JsonFile)dummy).getTopLevelValue();
final JsonValue newLiteral = ((JsonObject)top).findProperty("a").getValue();
PsiElement finalElement = element;
WriteAction.run(() -> finalElement.replace(newLiteral));
complete();
assertStringItems("completelyChanged", "preserve", "react");
});
}
@Override
public void testOneOfWithNotFilledPropertyValue() throws Exception {
baseCompletionTest("oneOfWithEnumValue", "oneOfWithEmptyPropertyValue", "business", "home");
}
public void testObjectLiteral() throws Exception {
baseInsertTest("insertArrayOrObjectLiteral", "objectLiteral");
complete();
assertStringItems("insideTopObject1", "insideTopObject2");
}
}

View File

@@ -0,0 +1,704 @@
// 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 org.jetbrains.yaml.schema;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.Predicate;
import com.jetbrains.jsonSchema.JsonSchemaHighlightingTestBase;
import org.intellij.lang.annotations.Language;
import org.jetbrains.yaml.YAMLLanguage;
import java.util.ArrayList;
import java.util.List;
public class YamlByJsonSchemaHighlightingTest extends JsonSchemaHighlightingTestBase {
@Override
public String getTestDataPath() {
return PathManagerEx.getCommunityHomePath() + "/plugins/yaml/testSrc/org/jetbrains/yaml/schema/data";
}
@Override
protected String getTestFileName() {
return "config.yml";
}
@Override
protected InspectionProfileEntry getInspectionProfile() {
return new YamlJsonSchemaHighlightingInspection();
}
@Override
protected Predicate<VirtualFile> getAvailabilityPredicate() {
return file -> file.getFileType() instanceof LanguageFileType && ((LanguageFileType)file.getFileType()).getLanguage().isKindOf(
YAMLLanguage.INSTANCE);
}
public void testEnum1() throws Exception {
@Language("JSON") final String schema = "{\"properties\": {\"prop\": {\"enum\": [1,2,3,\"18\"]}}}";
doTest(schema, "prop: 1");
doTest(schema, "prop: <warning>foo</warning>");
}
public void testMissingProp() throws Exception {
@Language("JSON") final String schema = "{\"properties\": {\"prop\": {}, \"flop\": {}}, \"required\": [\"flop\"]}";
doTest(schema, "<warning>prop: 2</warning>");
doTest(schema, "prop: 2\nflop: a");
doTest(schema, "flop: a");
}
public void testNumberMultipleWrong() throws Exception {
doTest("{ \"properties\": { \"prop\": {\"type\": \"number\", \"multipleOf\": 2}}}",
"prop: <warning descr=\"Schema validation: Is not multiple of 2\">3</warning>");
}
public void testNumberMultipleCorrect() throws Exception {
doTest("{ \"properties\": { \"prop\": {\"type\": \"number\", \"multipleOf\": 2}}}", "prop: 4");
}
public void testNumberMinMax() throws Exception {
doTest("{ \"properties\": { \"prop\": {\n" +
" \"type\": \"number\",\n" +
" \"minimum\": 0,\n" +
" \"maximum\": 100,\n" +
" \"exclusiveMaximum\": true\n" +
"}}}", "prop: 14");
}
@SuppressWarnings("Duplicates")
public void testEnum() throws Exception {
@Language("JSON") final String schema = "{\"properties\": {\"prop\": {\"enum\": [1,2,3,\"18\"]}}}";
doTest(schema, "prop: 18");
doTest(schema, "prop: 2");
doTest(schema, "prop: <warning descr=\"Schema validation: Value should be one of: [1, 2, 3, \\\"18\\\"]\">6</warning>");
}
@SuppressWarnings("Duplicates")
public void testSimpleString() throws Exception {
@Language("JSON") final String schema = "{\"properties\": {\"prop\": {\"type\": \"string\", \"minLength\": 2, \"maxLength\": 3}}}";
doTest(schema, "prop: <warning descr=\"Schema validation: String is shorter than 2\">s</warning>");
doTest(schema, "prop: sh");
doTest(schema, "prop: sho");
doTest(schema, "prop: <warning descr=\"Schema validation: String is longer than 3\">shor</warning>");
}
public void testArray() throws Exception {
@Language("JSON") final String schema = schema("{\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"type\": \"number\", \"minimum\": 18" +
" }\n" +
"}");
doTest(schema, "prop:\n - 101\n - 102");
doTest(schema, "prop:\n - <warning descr=\"Schema validation: Less than a minimum 18.0\">16</warning>");
doTest(schema, "prop:\n - <warning descr=\"Schema validation: Type is not allowed. Expected: number.\">test</warning>");
}
public void testTopLevelArray() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"type\": \"number\", \"minimum\": 18" +
" }\n" +
"}";
doTest(schema, "- 101\n- 102");
}
public void testTopLevelObjectArray() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"type\": \"object\", \"properties\": {\"a\": {\"type\": \"number\"}}" +
" }\n" +
"}";
doTest(schema, "- a: <warning descr=\"Schema validation: Type is not allowed. Expected: number.\">true</warning>");
doTest(schema, "- a: 18");
}
public void testArrayTuples1() throws Exception {
@Language("JSON") final String schema = schema("{\n" +
" \"type\": \"array\",\n" +
" \"items\": [{\n" +
" \"type\": \"number\", \"minimum\": 18" +
" }, {\"type\" : \"string\"}]\n" +
"}");
doTest(schema, "prop:\n - 101\n - 102");
@Language("JSON") final String schema2 = schema("{\n" +
" \"type\": \"array\",\n" +
" \"items\": [{\n" +
" \"type\": \"number\", \"minimum\": 18" +
" }, {\"type\" : \"string\"}],\n" +
"\"additionalItems\": false}");
doTest(schema2, "prop:\n - 101\n - 102\n - <warning descr=\"Schema validation: Additional items are not allowed\">additional</warning>");
}
public void testArrayLength() throws Exception {
@Language("JSON") final String schema = schema("{\"type\": \"array\", \"minItems\": 2, \"maxItems\": 3}");
doTest(schema, "prop:\n <warning descr=\"Schema validation: Array is shorter than 2\">- 1</warning>");
doTest(schema, "prop:\n - 1\n - 2");
doTest(schema, "prop:\n <warning descr=\"Schema validation: Array is longer than 3\">- 1\n - 2\n - 3\n - 4</warning>");
}
public void testArrayUnique() throws Exception {
@Language("JSON") final String schema = schema("{\"type\": \"array\", \"uniqueItems\": true}");
doTest(schema, "prop:\n - 1\n - 2");
doTest(schema, "prop:\n - <warning descr=\"Schema validation: Item is not unique\">1</warning>\n - 2\n - test\n - <warning descr=\"Schema validation: Item is not unique\">1</warning>");
}
public void testMetadataIsOk() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"title\" : \"Match anything\",\n" +
" \"description\" : \"This is a schema that matches anything.\",\n" +
" \"default\" : \"Default value\"\n" +
"}";
doTest(schema, "anything: 1");
}
public void testRequiredField() throws Exception {
@Language("JSON") final String schema = "{\"type\": \"object\", \"properties\": {\"a\": {}, \"b\": {}}, \"required\": [\"a\"]}";
doTest(schema, "a: 11");
doTest(schema, "a: 1\nb: true");
doTest(schema, "<warning descr=\"Schema validation: Missing required property 'a'\">b: alarm</warning>");
}
public void testInnerRequired() throws Exception {
@Language("JSON") final String schema = schema("{\"type\": \"object\", \"properties\": {\"a\": {}, \"b\": {}}, \"required\": [\"a\"]}");
doTest(schema, "prop:\n a: 11");
doTest(schema, "prop:\n a: 1\n b: true");
doTest(schema, "prop:\n <warning descr=\"Schema validation: Missing required property 'a'\">b: alarm</warning>");
}
public void testAdditionalPropertiesAllowed() throws Exception {
@Language("JSON") final String schema = schema("{}");
doTest(schema, "prop:\n q: true\n someStuff: 20");
}
public void testAdditionalPropertiesDisabled() throws Exception {
@Language("JSON") final String schema = "{\"type\": \"object\", \"properties\": {\"prop\": {}}, \"additionalProperties\": false}";
// not sure abt inner object
doTest(schema, "prop:\n q: true\n<warning descr=\"Schema validation: Property 'someStuff' is not allowed\">someStuff: 20</warning>");
}
public void testAdditionalPropertiesSchema() throws Exception {
@Language("JSON") final String schema = "{\"type\": \"object\", \"properties\": {\"a\": {}}," +
"\"additionalProperties\": {\"type\": \"number\"}}";
doTest(schema, "a: moo\nb: 5\nc: <warning descr=\"Schema validation: Type is not allowed. Expected: number.\">foo</warning>");
}
public void testMinMaxProperties() throws Exception {
@Language("JSON") final String schema = "{\"type\": \"object\", \"minProperties\": 2, \"maxProperties\": 3}";
doTest(schema, "<warning descr=\"Schema validation: Number of properties is less than 2\">a: 3</warning>");
doTest(schema, "a: 1\nb: 5");
doTest(schema, "<warning descr=\"Schema validation: Number of properties is greater than 3\">a: 1\nb: 22\nc: 333\nd: 4444</warning>");
}
@SuppressWarnings("Duplicates")
public void testOneOf() throws Exception {
final List<String> subSchemas = new ArrayList<>();
subSchemas.add("{\"type\": \"number\"}");
subSchemas.add("{\"type\": \"boolean\"}");
@Language("JSON") final String schema = schema("{\"oneOf\": [" + StringUtil.join(subSchemas, ", ") + "]}");
doTest(schema, "prop: 5");
doTest(schema, "prop: true");
doTest(schema, "prop: <warning descr=\"Schema validation: Type is not allowed. Expected one of: boolean, number.\">aaa</warning>");
}
@SuppressWarnings("Duplicates")
public void testOneOfForTwoMatches() throws Exception {
final List<String> subSchemas = new ArrayList<>();
subSchemas.add("{\"type\": \"string\", \"enum\": [\"a\", \"b\"]}");
subSchemas.add("{\"type\": \"string\", \"enum\": [\"a\", \"c\"]}");
@Language("JSON") final String schema = schema("{\"oneOf\": [" + StringUtil.join(subSchemas, ", ") + "]}");
doTest(schema, "prop: b");
doTest(schema, "prop: c");
doTest(schema, "prop: <warning descr=\"Schema validation: Validates to more than one variant\">a</warning>");
}
@SuppressWarnings("Duplicates")
public void testOneOfSelectError() throws Exception {
final List<String> subSchemas = new ArrayList<>();
subSchemas.add("{\"type\": \"string\",\n" +
" \"enum\": [\n" +
" \"off\", \"warn\", \"error\"\n" +
" ]}");
subSchemas.add("{\"type\": \"integer\"}");
@Language("JSON") final String schema = schema("{\"oneOf\": [" + StringUtil.join(subSchemas, ", ") + "]}");
doTest(schema, "prop: off");
doTest(schema, "prop: 12");
doTest(schema, "prop: <warning descr=\"Schema validation: Value should be one of: [\\\"off\\\", \\\"warn\\\", \\\"error\\\"]\">wrong</warning>");
}
@SuppressWarnings("Duplicates")
public void testAnyOf() throws Exception {
final List<String> subSchemas = new ArrayList<>();
subSchemas.add("{\"type\": \"string\", \"enum\": [\"a\", \"b\"]}");
subSchemas.add("{\"type\": \"string\", \"enum\": [\"a\", \"c\"]}");
@Language("JSON") final String schema = schema("{\"anyOf\": [" + StringUtil.join(subSchemas, ", ") + "]}");
doTest(schema, "prop: b");
doTest(schema, "prop: c");
doTest(schema, "prop: a");
doTest(schema, "prop: <warning descr=\"Schema validation: Value should be one of: [\\\"a\\\", \\\"b\\\"]\">d</warning>");
}
@SuppressWarnings("Duplicates")
public void testAllOf() throws Exception {
final List<String> subSchemas = new ArrayList<>();
subSchemas.add("{\"type\": \"integer\", \"multipleOf\": 2}");
subSchemas.add("{\"enum\": [1,2,3]}");
@Language("JSON") final String schema = schema("{\"allOf\": [" + StringUtil.join(subSchemas, ", ") + "]}");
doTest(schema, "prop: <warning descr=\"Schema validation: Is not multiple of 2\">1</warning>");
doTest(schema, "prop: <warning descr=\"Schema validation: Value should be one of: [1, 2, 3]\">4</warning>");
doTest(schema, "prop: 2");
}
// ----
public void testObjectInArray() throws Exception {
@Language("JSON") final String schema = schema("{\"type\": \"array\", \"items\": {\"type\": \"object\"," +
"\"properties\": {" +
"\"innerType\":{}, \"innerValue\":{}" +
"}, \"additionalProperties\": false" +
"}}");
doTest(schema, "prop:\n- innerType: aaa\n <warning descr=\"Schema validation: Property 'alien' is not allowed\">alien: bee</warning>");
}
public void testObjectDeeperInArray() throws Exception {
final String innerTypeSchema = "{\"properties\": {\"only\": {}}, \"additionalProperties\": false}";
@Language("JSON") final String schema = schema("{\"type\": \"array\", \"items\": {\"type\": \"object\"," +
"\"properties\": {" +
"\"innerType\":" + innerTypeSchema +
"}, \"additionalProperties\": false" +
"}}");
doTest(schema,
"prop:\n- innerType:\n only: true\n <warning descr=\"Schema validation: Property 'hidden' is not allowed\">hidden: false</warning>");
}
public void testInnerObjectPropValueInArray() throws Exception {
@Language("JSON") final String schema = "{\"properties\": {\"prop\": {\"type\": \"array\", \"items\": {\"enum\": [1,2,3]}}}}";
doTest(schema, "prop:\n - 1\n - 3");
doTest(schema, "prop:\n - <warning descr=\"Schema validation: Value should be one of: [1, 2, 3]\">out</warning>");
}
public void testAllOfProperties() throws Exception {
@Language("JSON") final String schema = "{\"allOf\": [{\"type\": \"object\", \"properties\": {\"first\": {}}}," +
" {\"properties\": {\"second\": {\"enum\": [33,44]}}}], \"additionalProperties\": false}";
// doTest(schema, "first: true\nsecond: <warning descr=\"Schema validation: Value should be one of: [33, 44]\">null</warning>");
doTest(schema, "first: true\nsecond: 44\n<warning descr=\"Schema validation: Property 'other' is not allowed\">other: 15</warning>");
doTest(schema, "first: true\nsecond: <warning descr=\"Schema validation: Value should be one of: [33, 44]\">12</warning>");
}
public void testWithWaySelection() throws Exception {
final String subSchema1 = "{\"enum\": [1,2,3,4,5]}";
final String subSchema2 = "{\"type\": \"array\", \"items\": {\"properties\": {\"kilo\": {}}, \"additionalProperties\": false}}";
@Language("JSON") final String schema = "{\"properties\": {\"prop\": {\"oneOf\": [" + subSchema1 + ", " + subSchema2 + "]}}}";
doTest(schema, "prop:\n - <warning descr=\"Schema validation: Property 'foxtrot' is not allowed\">foxtrot: 15</warning>\n kilo: 20");
}
public void testPatternPropertiesHighlighting() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"patternProperties\": {\n" +
" \"^A\" : {\n" +
" \"type\": \"number\"\n" +
" },\n" +
" \"B\": {\n" +
" \"type\": \"boolean\"\n" +
" },\n" +
" \"C\": {\n" +
" \"enum\": [\"test\", \"em\"]\n" +
" }\n" +
" }\n" +
"}";
doTest(schema, "Abezjana: 2\n" +
"Auto: <warning descr=\"Schema validation: Type is not allowed. Expected: number.\">no</warning>\n" +
"BAe: <warning descr=\"Schema validation: Type is not allowed. Expected: boolean.\">22</warning>\n" +
"Boloto: <warning descr=\"Schema validation: Type is not allowed. Expected: boolean.\">2</warning>\n" +
"Cyan: <warning descr=\"Schema validation: Value should be one of: [\\\"test\\\", \\\"em\\\"]\">me</warning>\n");
}
public void testPatternPropertiesFromIssue() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"type\": \"object\",\n" +
" \"additionalProperties\": false,\n" +
" \"patternProperties\": {\n" +
" \"p[0-9]\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"a[0-9]\": {\n" +
" \"enum\": [\"auto!\"]\n" +
" }\n" +
" }\n" +
"}";
doTest(schema,
"p1: 1\n" +
"p2: 3\n" +
"a2: auto!\n" +
"a1: <warning descr=\"Schema validation: Value should be one of: [\\\"auto!\\\"]\">moto!</warning>\n"
);
}
public void testPatternForPropertyValue() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"properties\": {\n" +
" \"withPattern\": {\n" +
" \"pattern\": \"p[0-9]\"\n" +
" }\n" +
" }\n" +
"}";
final String correctText = "withPattern: p1";
final String wrongText = "withPattern: <warning descr=\"Schema validation: String is violating the pattern: 'p[0-9]'\">wrong</warning>";
doTest(schema, correctText);
doTest(schema, wrongText);
}
public void testPatternWithSpecialEscapedSymbols() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"properties\": {\n" +
" \"withPattern\": {\n" +
" \"pattern\": \"^\\\\d{4}\\\\-(0?[1-9]|1[012])\\\\-(0?[1-9]|[12][0-9]|3[01])$\"\n" +
" }\n" +
" }\n" +
"}";
@Language("yaml") final String correctText = "withPattern: 1234-11-11";
final String wrongText = "withPattern: <warning descr=\"Schema validation: String is violating the pattern: '^\\d{4}\\-(0?[1-9]|1[012])\\-(0?[1-9]|[12][0-9]|3[01])$'\">wrong</warning>\n";
doTest(schema, correctText);
doTest(schema, wrongText);
}
// ---
public void testRootObjectRedefinedAdditionalPropertiesForbidden() throws Exception {
doTest(rootObjectRedefinedSchema(), "<warning descr=\"Schema validation: Property 'a' is not allowed\">a: true</warning>\n" +
"r1: allowed!");
}
public void testNumberOfSameNamedPropertiesCorrectlyChecked() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"properties\": {\n" +
" \"size\": {\n" +
" \"type\": \"object\",\n" +
" \"minProperties\": 2,\n" +
" \"maxProperties\": 3,\n" +
" \"properties\": {\n" +
" \"a\": {\n" +
" \"type\": \"boolean\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
doTest(schema,
"size: \n" +
" <warning descr=\"Schema validation: Number of properties is greater than 3\">a: <warning descr=\"Schema validation: Type is not allowed. Expected: boolean.\">1</warning>\n" +
" b: 3\n" +
" c: 4\n" +
" a: <warning descr=\"Schema validation: Type is not allowed. Expected: boolean.\">5</warning>" +
"</warning>\n");
}
public void testManyDuplicatesInArray() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"properties\": {\n" +
" \"array\":{\n" +
" \"type\": \"array\",\n" +
" \"uniqueItems\": true\n" +
" }\n" +
" }\n" +
"}";
doTest(schema, "array: \n" +
" - <warning descr=\"Schema validation: Item is not unique\">1</warning>\n" +
" - <warning descr=\"Schema validation: Item is not unique\">1</warning>\n" +
" - <warning descr=\"Schema validation: Item is not unique\">1</warning>\n" +
" - <warning descr=\"Schema validation: Item is not unique\">2</warning>\n" +
" - <warning descr=\"Schema validation: Item is not unique\">2</warning>\n" +
" - <warning descr=\"Schema validation: Item is not unique\">2</warning>\n" +
" - 5\n" +
" - <warning descr=\"Schema validation: Item is not unique\">3</warning>\n" +
" - <warning descr=\"Schema validation: Item is not unique\">3</warning>\n");
}
// ----
public void testPropertyValueAlsoHighlightedIfPatternIsInvalid() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"properties\": {\n" +
" \"withPattern\": {\n" +
" \"pattern\": \"^[]$\"\n" +
" }\n" +
" }\n" +
"}";
final String text = "withPattern:" +
" <warning descr=\"Schema validation: Can not check string by pattern because of error: Unclosed character class near index 3\n^[]$\n ^\">(124)555-4216</warning>";
doTest(schema, text);
}
public void testNotSchema() throws Exception {
@Language("JSON") final String schema = "{\"properties\": {\n" +
" \"not_type\": { \"not\": { \"type\": \"string\" } }\n" +
" }}";
doTest(schema, "not_type: <warning descr=\"Schema validation: Validates against 'not' schema\">wrong</warning>");
}
public void testNotSchemaCombinedWithNormal() throws Exception {
@Language("JSON") final String schema = "{\"properties\": {\n" +
" \"not_type\": {\n" +
" \"pattern\": \"^[a-z]*[0-5]*$\",\n" +
" \"not\": { \"pattern\": \"^[a-z]{1}[0-5]$\" }\n" +
" }\n" +
" }}";
doTest(schema, "not_type: va4");
doTest(schema, "not_type: <warning descr=\"Schema validation: Validates against 'not' schema\">a4</warning>");
doTest(schema, "not_type: <warning descr=\"Schema validation: String is violating the pattern: '^[a-z]*[0-5]*$'\">4a4</warning>");
}
public void testDoNotMarkOneOfThatDiffersWithFormat() throws Exception {
@Language("JSON") final String schema = "{\n" +
"\n" +
" \"properties\": {\n" +
" \"withFormat\": {\n" +
" \"type\": \"string\"," +
" \"oneOf\": [\n" +
" {\n" +
" \"format\":\"hostname\"\n" +
" },\n" +
" {\n" +
" \"format\": \"ip4\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
doTest(schema, "withFormat: localhost");
}
public void testAcceptSchemaWithoutType() throws Exception {
@Language("JSON") final String schema = "{\n" +
"\n" +
" \"properties\": {\n" +
" \"withFormat\": {\n" +
" \"oneOf\": [\n" +
" {\n" +
" \"format\":\"hostname\"\n" +
" },\n" +
" {\n" +
" \"format\": \"ip4\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
doTest(schema, "withFormat: localhost");
}
public void testArrayItemReference() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"items\": [\n" +
" {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" {\n" +
" \"$ref\": \"#/items/0\"\n" +
" }\n" +
" ]\n" +
"}";
doTest(schema, "- 1\n- 2");
doTest(schema, "- 1\n- <warning>foo</warning>");
}
public void testValidateAdditionalItems() throws Exception {
@Language("JSON") final String schema = "{\n" +
" \"definitions\": {\n" +
" \"options\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"type\": \"number\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"items\": [\n" +
" {\n" +
" \"type\": \"boolean\"\n" +
" },\n" +
" {\n" +
" \"type\": \"boolean\"\n" +
" }\n" +
" ],\n" +
" \"additionalItems\": {\n" +
" \"$ref\": \"#/definitions/options/items\"\n" +
" }\n" +
"}";
doTest(schema, "- true\n- true");
doTest(schema, "- true\n- true\n- 1\n- 2\n- 3");
doTest(schema, "- true\n- true\n- 1\n- <warning>qq</warning>");
}
public void testExclusiveMinMaxV6() throws Exception {
@Language("JSON") String exclusiveMinSchema = "{\"properties\": {\"prop\": {\"exclusiveMinimum\": 3}}}";
doTest(exclusiveMinSchema, "prop: <warning>2</warning>");
doTest(exclusiveMinSchema, "prop: <warning>3</warning>");
doTest(exclusiveMinSchema, "prop: 4");
@Language("JSON") String exclusiveMaxSchema = "{\"properties\": {\"prop\": {\"exclusiveMaximum\": 3}}}";
doTest(exclusiveMaxSchema, "prop: 2");
doTest(exclusiveMaxSchema, "prop: <warning>3</warning>");
doTest(exclusiveMaxSchema, "prop: <warning>4</warning>");
}
/*todo later
public void testPropertyNamesV6() throws Exception {
doTest("{\"propertyNames\": {\"minLength\": 7}}", "{<warning>\"prop\"</warning>: 2}");
doTest("{\"properties\": {\"prop\": {\"propertyNames\": {\"minLength\": 7}}}}", "{\"prop\": {<warning>\"qq\"</warning>: 7}}");
}*/
public void testContainsV6() throws Exception {
@Language("JSON") String schema = "{\"properties\": {\"prop\": {\"type\": \"array\", \"contains\": {\"type\": \"number\"}}}}";
doTest(schema, "prop:\n <warning>- a\n - true</warning>");
doTest(schema, "prop:\n - a\n - true\n - 1");
}
public void testConstV6() throws Exception {
@Language("JSON") String schema = "{\"properties\": {\"prop\": {\"type\": \"string\", \"const\": \"foo\"}}}";
doTest(schema, "prop: <warning>a</warning>");
doTest(schema, "prop: <warning>5</warning>");
doTest(schema, "prop: foo");
}
public void testIfThenElseV7() throws Exception {
@Language("JSON") String schema = "{\n" +
" \"if\": {\n" +
" \"properties\": {\n" +
" \"a\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" },\n" +
" \"required\": [\"a\"]\n" +
" },\n" +
" \"then\": {\n" +
" \"properties\": {\n" +
" \"b\": {\n" +
" \"type\": \"number\"\n" +
" }\n" +
" },\n" +
" \"required\": [\"b\"]\n" +
" },\n" +
" \"else\": {\n" +
" \"properties\": {\n" +
" \"c\": {\n" +
" \"type\": \"boolean\"\n" +
" }\n" +
" },\n" +
" \"required\": [\"c\"]\n" +
" }\n" +
"}";
doTest(schema, "c: <warning>5</warning>");
doTest(schema, "c: true");
doTest(schema, "<warning>a: a\nc: true</warning>");
doTest(schema, "a: a\nb: <warning>true</warning>");
doTest(schema, "a: a\nb: 5");
}
public void testNestedOneOf() throws Exception {
@Language("JSON") String schema = "{\"type\":\"object\",\n" +
" \"oneOf\": [\n" +
" {\n" +
" \"properties\": {\n" +
" \"type\": {\n" +
" \"type\": \"string\",\n" +
" \"oneOf\": [\n" +
" {\n" +
" \"pattern\": \"(good)\"\n" +
" },\n" +
" {\n" +
" \"pattern\": \"(ok)\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
" },\n" +
" {\n" +
" \"properties\": {\n" +
" \"type\": {\n" +
" \"type\": \"string\",\n" +
" \"pattern\": \"^(fine)\"\n" +
" },\n" +
" \"extra\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" },\n" +
" \"required\": [\"type\", \"extra\"]\n" +
" }\n" +
" ]}";
doTest(schema, "type: good");
doTest(schema, "type: ok");
doTest(schema, "type: <warning>doog</warning>");
doTest(schema, "type: <warning>ko</warning>");
}
public void testArrayRefs() throws Exception {
@Language("JSON") String schema = "{\n" +
" \"myDefs\": {\n" +
" \"myArray\": [\n" +
" {\n" +
" \"type\": \"number\"\n" +
" },\n" +
" {\n" +
" \"type\": \"boolean\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"type\": \"array\",\n" +
" \"items\": [\n" +
" {\n" +
" \"$ref\": \"#/myDefs/myArray/0\"\n" +
" },\n" +
" {\n" +
" \"$ref\": \"#/myDefs/myArray/1\"\n" +
" }\n" +
" ]\n" +
"}";
doTest(schema, "- 1\n- <warning>2</warning>");
doTest(schema, "- <warning>a</warning>\n- <warning>2</warning>");
doTest(schema, "- <warning>a</warning>\n- true");
doTest(schema, "- 1\n- false");
}
public void testWithTags() throws Exception {
@Language("JSON") String schema = "{\"properties\": { \"platform\": { \"enum\": [\"x86\", \"x64\"] } }}";
doTest(schema, "platform:\n !!str x64");
doTest(schema, "platform:\n <warning>a x64</warning>");
}
static String schema(final String s) {
return "{\"type\": \"object\", \"properties\": {\"prop\": " + s + "}}";
}
public static String rootObjectRedefinedSchema() {
return "{\n" +
" \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n" +
" \"type\": \"object\",\n" +
" \"$ref\" : \"#/definitions/root\",\n" +
" \"definitions\": {\n" +
" \"root\" : {\n" +
" \"type\": \"object\",\n" +
" \"additionalProperties\": false,\n" +
" \"properties\": {\n" +
" \"r1\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"r2\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n";
}
}

View File

@@ -0,0 +1,18 @@
// 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 org.jetbrains.yaml.schema;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
@SuppressWarnings({"JUnitTestCaseWithNoTests", "JUnitTestClassNamingConvention"})
public class YamlJsonSchemaTestSuite extends TestCase {
public static Test suite() {
final TestSuite suite = new TestSuite(YamlJsonSchemaTestSuite.class.getSimpleName());
suite.addTestSuite(YamlByJsonSchemaDocumentationTest.class);
suite.addTestSuite(YamlByJsonSchemaCompletionTest.class);
suite.addTestSuite(YamlByJsonSchemaHeavyCompletionTest.class);
suite.addTestSuite(YamlByJsonSchemaHighlightingTest.class);
return suite;
}
}

View File

@@ -0,0 +1,10 @@
{
"properties": {
"jsx": {
"oneOf": [
{"enum": [ "preserve", "react" ]},
{"enum": [ "react-native" ]}
]
}
}
}

View File

@@ -0,0 +1,17 @@
{
"properties": {
"top": {
"type": "array",
"items": {
"enum": [1,2,3]
}
},
"topObject": {
"type": "object",
"properties": {
"insideTopObject1": {},
"insideTopObject2": {}
}
}
}
}

View File

@@ -0,0 +1,7 @@
{
"properties": {
"jsx": {
"enum": [ "preserve", "react", "react-native" ]
}
}
}

View File

@@ -0,0 +1,28 @@
{
"properties": {
"jsx": {
"enum": [ "preserve", "react", "react-native" ]
},
"withStringDefaultValue": {
"default": "stringDefault"
},
"withIntegerDefaultValue": {
"default": 118
},
"integerType": {
"type": "integer"
},
"stringType": {
"type": "string"
},
"objectType": {
"type": "object"
},
"booleanType": {
"type": "boolean"
},
"arrayType": {
"type": "array"
}
}
}

View File

@@ -0,0 +1,2 @@
booleanTy<caret>
someOtherProperty: false

View File

@@ -0,0 +1,2 @@
booleanType: <selection>false<caret></selection>
someOtherProperty: false

View File

@@ -0,0 +1,2 @@
booleanType: <selection>false<caret></selection>

View File

@@ -0,0 +1 @@
withStringDefaultValue: <selection>stringDefault<caret></selection>

View File

@@ -0,0 +1,2 @@
integerTy<caret>
someOtherProperty: 1

View File

@@ -0,0 +1,2 @@
integerType:<caret>
someOtherProperty: 1

View File

@@ -0,0 +1,2 @@
withIntegerDefault<caret>
someOtherProperty: 112

View File

@@ -0,0 +1,2 @@
withIntegerDefaultValue: <selection>118<caret></selection>
someOtherProperty: 112

View File

@@ -0,0 +1 @@
withIntegerDefaultValue: <selection>118<caret></selection>

View File

@@ -0,0 +1,2 @@
withStringDefaultValue: <selection>stringDefault<caret></selection>
some: 2

View File

@@ -0,0 +1 @@
withStringDefaultValue: <selection>stringDefault<caret></selection>

View File

@@ -0,0 +1 @@
withStringDefaultValue: <selection>stringDefault<caret></selection>

View File

@@ -0,0 +1,2 @@
stringTy<caret>
someOtherProperty: 123

View File

@@ -0,0 +1,2 @@
stringType:<caret>
someOtherProperty: 123

View File

@@ -0,0 +1,55 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"address": {
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
},
"office_address": {
"properties": {
"type": { "enum": [ "business" ] },
"building": { "type": "string" }
},
"required": ["type", "building"],
"additionalProperties": false
},
"home_address": {
"properties": {
"type": { "enum": [ "home" ] },
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["type", "street_address", "city", "state"],
"additionalProperties": false
},
"partOne": {
"properties": {"one": {"type": "string"}}
},
"partTwo": {
"properties": {"two": {"type": "integer"}}
}
},
"type": "object",
"properties": {
"client_address": {
"oneOf": [
{ "$ref": "#/definitions/office_address" },
{ "$ref": "#/definitions/home_address" }
]
},
"parts": {
"oneOf": [
{ "$ref": "#/definitions/partOne" },
{ "$ref": "#/definitions/partTwo" }
],
"additionalProperties": false
}
}
}

View File

@@ -0,0 +1,4 @@
client_address:
building: 1
type: <caret>

View File

@@ -0,0 +1 @@
<b>subscriptions</b>: object<br/><br/>All server-side subscriptions

View File

@@ -0,0 +1,2 @@
su<caret>bscriptions:

View File

@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"title": "JSON schema for Prisma prisma.yml files",
"definitions": {
"subscription": {
"description": "A piece of code that you should run."
}
},
"properties": {
"subscriptions": {
"description": "All server-side subscriptions",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/subscription"
}
}
}
}

View File

@@ -0,0 +1 @@
<b>subscriptions</b>: object<br/><br/>All server-side subscriptions