RUBY-23497: yaml by json schema - support empty file, empty object and adjust quick fixes

This commit is contained in:
Anton Lobov
2019-01-31 14:35:39 +01:00
parent 5d44fb1e50
commit e376d51da9
13 changed files with 328 additions and 40 deletions
@@ -53,6 +53,8 @@ public interface JsonLikePsiWalker {
return element.getTextRange();
}
default boolean acceptsEmptyRoot() { return false; }
@Nullable
static JsonLikePsiWalker getWalker(@NotNull final PsiElement element, JsonSchemaObject schemaObject) {
if (JSON_ORIGINAL_PSI_WALKER.handles(element)) return JSON_ORIGINAL_PSI_WALKER;
@@ -2,6 +2,8 @@
package com.jetbrains.jsonSchema.extension;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.jetbrains.jsonSchema.impl.JsonSchemaType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -9,8 +11,11 @@ public interface JsonLikeSyntaxAdapter {
@Nullable PsiElement getPropertyValue(PsiElement property);
@NotNull default PsiElement adjustValue(@NotNull PsiElement value) { return value; }
@Nullable String getPropertyName(PsiElement property);
@NotNull PsiElement createProperty(@NotNull final String name, @NotNull final String value);
boolean ensureComma(PsiElement backward, PsiElement self, PsiElement newElement);
@NotNull PsiElement createProperty(@NotNull final String name, @NotNull final String value, PsiElement element);
boolean ensureComma(PsiElement self, PsiElement newElement);
void removeIfComma(PsiElement forward);
boolean fixWhitespaceBefore(PsiElement initialElement, PsiElement element);
@NotNull String getDefaultValueFromType(@Nullable JsonSchemaType type);
PsiElement adjustNewProperty(PsiElement element);
PsiElement adjustPropertyAnchor(LeafPsiElement element);
}
@@ -12,6 +12,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.IncorrectOperationException;
import com.intellij.util.ThreeState;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.extension.JsonLikeSyntaxAdapter;
@@ -193,14 +194,14 @@ public class JsonOriginalPsiWalker implements JsonLikePsiWalker {
@NotNull
@Override
public PsiElement createProperty(@NotNull String name, @NotNull String value) {
public PsiElement createProperty(@NotNull String name, @NotNull String value, PsiElement element) {
return myGenerator.createProperty(name, value);
}
@Override
public boolean ensureComma(PsiElement backward, PsiElement self, PsiElement newElement) {
if (backward instanceof JsonProperty) {
self.addAfter(myGenerator.createComma(), backward);
public boolean ensureComma(PsiElement self, PsiElement newElement) {
if (newElement instanceof JsonProperty && self instanceof JsonProperty) {
self.getParent().addAfter(myGenerator.createComma(), self);
return true;
}
return false;
@@ -217,6 +218,22 @@ public class JsonOriginalPsiWalker implements JsonLikePsiWalker {
public boolean fixWhitespaceBefore(PsiElement initialElement, PsiElement element) {
return true;
}
@NotNull
@Override
public String getDefaultValueFromType(@Nullable JsonSchemaType type) {
return type == null ? "" : type.getDefaultValue();
}
@Override
public PsiElement adjustNewProperty(PsiElement element) {
return element;
}
@Override
public PsiElement adjustPropertyAnchor(LeafPsiElement element) {
throw new IncorrectOperationException("Shouldn't use leafs for insertion in pure JSON!");
}
};
}
@@ -300,7 +300,7 @@ class JsonSchemaAnnotatorChecker {
if (object.shouldCheckIntegralRequirements()) {
final Set<String> required = schema.getRequired();
if (required != null) {
HashSet<String> requiredNames = ContainerUtil.newHashSet(required);
HashSet<String> requiredNames = ContainerUtil.newLinkedHashSet(required);
requiredNames.removeAll(set);
if (!requiredNames.isEmpty()) {
JsonValidationError.MissingMultiplePropsIssueData data = createMissingPropertiesData(schema, requiredNames);
@@ -131,7 +131,7 @@ public class JsonSchemaComplianceChecker {
myHolder.registerProblem(psiElement, range, value);
}
else {
myHolder.registerProblem(psiElement, range, value, fix);
myHolder.registerProblem(range.isEmpty() ? psiElement.getContainingFile() : psiElement, range, value, fix);
}
}
@@ -142,7 +142,7 @@ public class JsonSchemaComplianceChecker {
if (!isTop) ref.set(el);
return isTop;
});
return ref.isNull() ? null : walker.createValueAdapter(ref.get());
return ref.isNull() ? (walker.acceptsEmptyRoot() ? walker.createValueAdapter(element) : null) : walker.createValueAdapter(ref.get());
}
private boolean checkIfAlreadyProcessed(@NotNull PsiElement property) {
@@ -408,9 +408,9 @@ public class JsonSchemaReader {
private static MyReader createRequired() {
return (element, object, queue) -> {
if (element instanceof JsonArray) {
object.setRequired(((JsonArray)element).getValueList().stream()
object.setRequired(ContainerUtil.newLinkedHashSet(((JsonArray)element).getValueList().stream()
.filter(notEmptyString())
.map(el -> StringUtil.unquoteString(el.getText())).collect(Collectors.toSet()));
.map(el -> StringUtil.unquoteString(el.getText())).collect(Collectors.toList())));
}
};
}
@@ -19,12 +19,14 @@ import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
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.DocumentUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.jsonSchema.extension.JsonLikeSyntaxAdapter;
import com.jetbrains.jsonSchema.impl.JsonSchemaType;
import com.jetbrains.jsonSchema.impl.JsonValidationError;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -60,12 +62,13 @@ public class AddMissingPropertyFix implements LocalQuickFix, BatchQuickFix<Commo
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
Ref<Boolean> hadComma = Ref.create(false);
VirtualFile file = element.getContainingFile().getVirtualFile();
PsiElement newElement = performFix(element, hadComma);
// if we have more than one property, don't expand templates and don't move the caret
if (newElement == null) return;
PsiElement value = myQuickFixAdapter.getPropertyValue(newElement);
FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(element.getContainingFile().getVirtualFile());
FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(file);
EditorEx editor = EditorUtil.getEditorEx(fileEditor);
assert editor != null;
if (value == null) {
@@ -83,7 +86,12 @@ public class AddMissingPropertyFix implements LocalQuickFix, BatchQuickFix<Commo
? new MacroCallNode(new CompleteMacro())
: isEmptyArray ? new EmptyNode() : new ConstantNode(goInside ? StringUtil.unquoteString(text) : text));
editor.getCaretModel().moveToOffset(newElement.getTextRange().getStartOffset());
builder.setEndVariableAfter(newElement);
if (PsiTreeUtil.nextLeaf(newElement) != null) {
builder.setEndVariableAfter(newElement);
}
else {
builder.setEndVariableBefore(newElement.getLastChild());
}
WriteAction.run(() -> {
Template template = builder.buildInlineTemplate();
template.setToReformat(true);
@@ -92,23 +100,43 @@ public class AddMissingPropertyFix implements LocalQuickFix, BatchQuickFix<Commo
}
@Nullable
public PsiElement performFix(@Nullable PsiElement element, @NotNull Ref<Boolean> hadComma) {
if (element == null) return null;
public PsiElement performFix(@Nullable PsiElement node, @NotNull Ref<Boolean> hadComma) {
if (node == null) return null;
PsiElement element = node instanceof PsiFile ? node.getFirstChild() : node;
Ref<PsiElement> newElementRef = Ref.create(null);
WriteAction.run(() -> {
boolean isSingle = myData.myMissingPropertyIssues.size() == 1;
for (JsonValidationError.MissingPropertyIssueData issue: myData.myMissingPropertyIssues) {
PsiElement processedElement = element;
List<JsonValidationError.MissingPropertyIssueData> reverseOrder
= ContainerUtil.reverse(ContainerUtil.newArrayList(myData.myMissingPropertyIssues));
for (JsonValidationError.MissingPropertyIssueData issue: reverseOrder) {
Object defaultValueObject = issue.defaultValue;
String defaultValue = defaultValueObject instanceof String ? StringUtil.wrapWithDoubleQuote(defaultValueObject.toString()) : null;
PsiElement newElement = element
.addBefore(
myQuickFixAdapter.createProperty(issue.propertyName, defaultValue == null ? getDefaultValueFromType(issue) : defaultValue),
element.getLastChild());
PsiElement backward = PsiTreeUtil.skipWhitespacesBackward(newElement);
hadComma.set(myQuickFixAdapter.ensureComma(backward, element, newElement));
PsiElement property = myQuickFixAdapter.createProperty(issue.propertyName, defaultValue == null
? myQuickFixAdapter
.getDefaultValueFromType(issue.propertyType)
: defaultValue, element);
PsiElement newElement;
if (processedElement instanceof LeafPsiElement) {
newElement = myQuickFixAdapter.adjustPropertyAnchor((LeafPsiElement)processedElement).addBefore(property, null);
}
else {
if (processedElement == element) {
newElement = processedElement.addBefore(property, processedElement.getLastChild());
}
else {
newElement = processedElement.getParent().addBefore(property, processedElement);
}
}
PsiElement adjusted = myQuickFixAdapter.adjustNewProperty(newElement);
hadComma.set(myQuickFixAdapter.ensureComma(adjusted, PsiTreeUtil.skipWhitespacesForward(newElement)));
if (!hadComma.get()) {
hadComma.set(processedElement == element && myQuickFixAdapter.ensureComma(PsiTreeUtil.skipWhitespacesBackward(newElement), adjusted));
}
processedElement = adjusted;
if (isSingle) {
newElementRef.set(newElement);
newElementRef.set(adjusted);
}
}
});
@@ -116,12 +144,6 @@ public class AddMissingPropertyFix implements LocalQuickFix, BatchQuickFix<Commo
return newElementRef.get();
}
@NotNull
private static String getDefaultValueFromType(JsonValidationError.MissingPropertyIssueData issue) {
JsonSchemaType propertyType = issue.propertyType;
return propertyType == null ? "" : propertyType.getDefaultValue();
}
@Override
public boolean startInWriteAction() {
return false;
@@ -0,0 +1,74 @@
// Copyright 2000-2019 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.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 java.util.List;
public class YamlEmptyObjectAdapter implements JsonObjectValueAdapter {
private final PsiElement myElement;
public YamlEmptyObjectAdapter(PsiElement element) {
myElement = element;
}
@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 List<JsonPropertyAdapter> getPropertyList() {
return ContainerUtil.emptyList();
}
@Override
public boolean isNull() {
return false;
}
@NotNull
@Override
public PsiElement getDelegate() {
return myElement;
}
@Nullable
@Override
public JsonObjectValueAdapter getAsObject() {
return this;
}
@Nullable
@Override
public JsonArrayValueAdapter getAsArray() {
return null;
}
}
@@ -12,11 +12,13 @@ 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.psi.util.PsiUtilCore;
import com.intellij.util.ThreeState;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.extension.JsonLikeSyntaxAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter;
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter;
import com.jetbrains.jsonSchema.impl.JsonSchemaType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.YAMLElementGenerator;
@@ -67,6 +69,11 @@ public class YamlJsonPsiWalker implements JsonLikePsiWalker {
return element instanceof YAMLFile || element instanceof YAMLDocument;
}
@Override
public boolean acceptsEmptyRoot() {
return true;
}
@Override
public PsiElement findElementToCheck(@NotNull PsiElement element) {
PsiElement current = element;
@@ -87,7 +94,8 @@ public class YamlJsonPsiWalker implements JsonLikePsiWalker {
@Nullable
@Override
public JsonValueAdapter createValueAdapter(@NotNull PsiElement element) {
return element instanceof YAMLValue ? YamlPropertyAdapter.createValueAdapterByType((YAMLValue)element) : null;
return element instanceof YAMLValue ? YamlPropertyAdapter.createValueAdapterByType((YAMLValue)element)
: (element instanceof YAMLDocument ? new YamlEmptyObjectAdapter(element) : null);
}
@Override
@@ -229,7 +237,7 @@ public class YamlJsonPsiWalker implements JsonLikePsiWalker {
assert property instanceof YAMLKeyValue;
YAMLValue value = ((YAMLKeyValue)property).getValue();
if (value == null) return null;
return adjustValue(property);
return adjustValue(value);
}
@NotNull
@@ -249,16 +257,30 @@ public class YamlJsonPsiWalker implements JsonLikePsiWalker {
return ((YAMLKeyValue)property).getName();
}
private YAMLKeyValue findPrecedingKeyValueWithNoValue(PsiElement element) {
if (PsiUtilCore.getElementType(element) == YAMLTokenTypes.INDENT) {
PsiElement prev = element.getPrevSibling();
prev = prev == null ? null : prev.getPrevSibling();
if (prev instanceof YAMLKeyValue && ((YAMLKeyValue)prev).getValue() == null) {
return (YAMLKeyValue)prev;
}
}
return null;
}
@NotNull
@Override
public PsiElement createProperty(@NotNull String name, @NotNull String value) {
return myGenerator.createYamlKeyValue(name, StringUtil.unquoteString(value));
public PsiElement createProperty(@NotNull String name, @NotNull String value, PsiElement element) {
YAMLKeyValue keyValue = myGenerator.createYamlKeyValue(name, StringUtil.unquoteString(value));
return element instanceof YAMLDocument || findPrecedingKeyValueWithNoValue(element) != null
? myGenerator.createDummyYamlWithText(keyValue.getText()).getDocuments().get(0).getFirstChild()
: keyValue;
}
@Override
public boolean ensureComma(PsiElement backward, PsiElement self, PsiElement newElement) {
if (newElement instanceof YAMLKeyValue) {
newElement.getParent().addAfter(myGenerator.createEol(), newElement);
public boolean ensureComma(PsiElement self, PsiElement newElement) {
if (newElement instanceof YAMLKeyValue && self instanceof YAMLKeyValue) {
self.getParent().addAfter(myGenerator.createEol(), self);
}
return false;
}
@@ -279,6 +301,33 @@ public class YamlJsonPsiWalker implements JsonLikePsiWalker {
public boolean fixWhitespaceBefore(PsiElement initialElement, PsiElement element) {
return initialElement instanceof YAMLValue && initialElement != element;
}
@NotNull
@Override
public String getDefaultValueFromType(@Nullable JsonSchemaType type) {
if (type == null) return "";
if (type == JsonSchemaType._object) return " ";
if (type == JsonSchemaType._array) return " - ";
return type.getDefaultValue();
}
@Override
public PsiElement adjustNewProperty(PsiElement element) {
if (element instanceof YAMLMapping) return element.getFirstChild();
return element;
}
@Override
public PsiElement adjustPropertyAnchor(LeafPsiElement element) {
YAMLKeyValue keyValue = findPrecedingKeyValueWithNoValue(element);
assert keyValue != null: "Should come here only for YAMLKeyValue with no value and a following indent";
keyValue.addBefore(myGenerator.createEol(), null);
keyValue.addBefore(myGenerator.createIndent(element.getTextLength()), null);
PsiElement prev = element.getPrevSibling();
if (prev != null) prev.delete();
element.delete();
return keyValue;
}
};
}
@@ -21,6 +21,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.YAMLBundle;
import org.jetbrains.yaml.psi.YAMLDocument;
import org.jetbrains.yaml.psi.YAMLFile;
import org.jetbrains.yaml.psi.YAMLValue;
import org.jetbrains.yaml.psi.YamlPsiElementVisitor;
import javax.swing.*;
@@ -54,9 +55,9 @@ public class YamlJsonSchemaHighlightingInspection extends LocalInspectionTool {
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;
YAMLDocument document = documents.get(0);
YAMLValue topLevelValue = document.getTopLevelValue();
PsiElement root = topLevelValue == null ? document : topLevelValue;
JsonSchemaService service = JsonSchemaService.Impl.get(file.getProject());
VirtualFile virtualFile = file.getViewProvider().getVirtualFile();
if (!service.isApplicableToFile(virtualFile)) return PsiElementVisitor.EMPTY_VISITOR;
@@ -4,6 +4,7 @@ package org.jetbrains.yaml.schema;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.TokenType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.jsonSchema.extension.adapters.JsonObjectValueAdapter;
@@ -11,6 +12,7 @@ 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.YAMLTokenTypes;
import org.jetbrains.yaml.psi.*;
import java.util.Collection;
@@ -71,6 +73,16 @@ public class YamlPropertyAdapter implements JsonPropertyAdapter {
@Nullable
public static JsonValueAdapter createEmptyValueAdapter(@NotNull PsiElement context, boolean pinSelf) {
if (context instanceof YAMLKeyValue && ((YAMLKeyValue)context).getValue() == null) {
PsiElement next = PsiTreeUtil.skipWhitespacesForward(context);
if (PsiUtilCore.getElementType(next) == YAMLTokenTypes.EOL) {
next = PsiTreeUtil.skipWhitespacesForward(next);
if (PsiUtilCore.getElementType(next) == YAMLTokenTypes.INDENT && !(PsiTreeUtil.skipWhitespacesForward(next) instanceof YAMLKeyValue)) {
// potentially empty object after newline+indent
return new YamlEmptyObjectAdapter(next);
}
}
}
PsiElement nextSibling = context.getNextSibling();
PsiElement nodeToHighlight = PsiUtilCore.getElementType(nextSibling) == TokenType.WHITE_SPACE
? nextSibling
@@ -844,4 +844,45 @@ public class YamlByJsonSchemaHighlightingTest extends JsonSchemaHighlightingTest
" <warning descr=\"Schema validation: Type is not allowed. Expected: string.\">-</warning>\n" +
" - a");
}
public void testEmptyFile() throws Exception {
doTest("{\n" +
" \"type\": \"object\",\n" +
"\n" +
" \"properties\": {\n" +
" \"versionAsStringArray\": {\n" +
" \"type\": \"array\"\n" +
" }\n" +
" },\n" +
" \"required\": [\"versionAsStringArray\"]\n" +
"}", "<warning descr=\"Schema validation: Missing required property 'versionAsStringArray'\"></warning>");
}
public void testEmptyValueBetweenProps() throws Exception {
doTest("{\n" +
" \"type\": \"object\",\n" +
"\n" +
" \"properties\": {\n" +
" \"versionAsStringArray\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"xxx\": {\n" +
" \"type\": \"number\"\n" +
" },\n" +
" \"yyy\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"zzz\": {\n" +
" \"type\": \"number\"\n" +
" }\n" +
" },\n" +
" \"required\": [\"xxx\", \"yyy\", \"zzz\"]\n" +
" }\n" +
" },\n" +
" \"required\": [\"versionAsStringArray\"]\n" +
"}", "versionAsStringArray:\n" +
" zzz: 0\n" +
" yyy:<warning descr=\"Schema validation: Type is not allowed. Expected: string.\"> </warning>\n" +
" xxx: 0");
}
}
@@ -48,4 +48,69 @@ public class YamlByJsonSchemaQuickFixTest extends JsonSchemaQuickFixTestBase {
"}", "a: 5\n<warning>b: 6</warning>\nc: 7", "Remove prohibited property 'b'", "a: 5\n" +
"c: 7");
}
public void testEmptyFile() throws Exception {
doTest("{\n" +
" \"type\": \"object\",\n" +
"\n" +
" \"properties\": {\n" +
" \"versionAsStringArray\": {\n" +
" \"type\": \"array\"\n" +
" }\n" +
" },\n" +
" \"required\": [\"versionAsStringArray\"]\n" +
"}", "<warning></warning>", "Add missing property 'versionAsStringArray'", "versionAsStringArray:\n" +
" - ");
}
public void testEmptyObject() throws Exception {
doTest("{\n" +
" \"type\": \"object\",\n" +
"\n" +
" \"properties\": {\n" +
" \"versionAsStringArray\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"xxx\": {\n" +
" \"type\": \"array\"\n" +
" }\n" +
" },\n" +
" \"required\": [\"xxx\"]\n" +
" }\n" +
" },\n" +
" \"required\": [\"versionAsStringArray\"]\n" +
"}", "versionAsStringArray:\n" +
"<warning> </warning>", "Add missing property 'xxx'", "versionAsStringArray:\n" +
" xxx:\n" +
" - ");
}
public void testEmptyObjectMultipleProps() throws Exception {
doTest("{\n" +
" \"type\": \"object\",\n" +
"\n" +
" \"properties\": {\n" +
" \"versionAsStringArray\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"xxx\": {\n" +
" \"type\": \"number\"\n" +
" },\n" +
" \"yyy\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"zzz\": {\n" +
" \"type\": \"number\"\n" +
" }\n" +
" },\n" +
" \"required\": [\"xxx\", \"yyy\", \"zzz\"]\n" +
" }\n" +
" },\n" +
" \"required\": [\"versionAsStringArray\"]\n" +
"}", "versionAsStringArray:\n" +
"<warning> </warning>","Add missing properties 'xxx', 'yyy', 'zzz'", "versionAsStringArray:\n" +
" xxx: 0\n" +
" yyy:\n" +
" zzz: 0");
}
}