package com.github.tinselspoon.intellij.kubernetes.codeInsight; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; import org.jetbrains.yaml.psi.YAMLKeyValue; import org.jetbrains.yaml.psi.YAMLMapping; import org.jetbrains.yaml.psi.YAMLSequence; import org.jetbrains.yaml.psi.YAMLSequenceItem; import com.github.tinselspoon.intellij.kubernetes.KubernetesYamlPsiUtil; import com.github.tinselspoon.intellij.kubernetes.ResourceTypeKey; import com.github.tinselspoon.intellij.kubernetes.model.Model; import com.github.tinselspoon.intellij.kubernetes.model.ModelProvider; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; import com.intellij.psi.PsiElement; /** * Inspects {@link YAMLKeyValue}s with a {@link YAMLMapping} value, and raises an error if the corresponding schema element declares properties that are required but are not in the mapping. */ public class MissingRequiredPropertiesAnnotator implements Annotator { @Override public void annotate(@NotNull final PsiElement element, @NotNull final AnnotationHolder annotationHolder) { if (!KubernetesYamlPsiUtil.isKubernetesFile(element)) { return; } final ModelProvider modelProvider = ModelProvider.INSTANCE; final ResourceTypeKey resourceKey = KubernetesYamlPsiUtil.findResourceKey(element); if (resourceKey != null && element instanceof YAMLKeyValue) { final YAMLKeyValue keyValue = (YAMLKeyValue) element; final Model model = KubernetesYamlPsiUtil.modelForKey(modelProvider, resourceKey, keyValue); if (model != null && keyValue.getKey() != null) { if (keyValue.getValue() instanceof YAMLMapping) { final YAMLMapping mapping = (YAMLMapping) keyValue.getValue(); addErrors(annotationHolder, model, keyValue.getKey(), mapping); } else if (keyValue.getValue() instanceof YAMLSequence) { final YAMLSequence sequence = (YAMLSequence) keyValue.getValue(); for (final YAMLSequenceItem item : sequence.getItems()) { if (item.getValue() instanceof YAMLMapping) { addErrors(annotationHolder, model, item.getFirstChild(), (YAMLMapping) item.getValue()); } } } } } } private void addErrors(final @NotNull AnnotationHolder annotationHolder, final Model model, final PsiElement errorTarget, final YAMLMapping mapping) { final Set<String> existingKeys = mapping.getKeyValues().stream().map(YAMLKeyValue::getKeyText).collect(Collectors.toSet()); // Find out the keys that are needed, and remove any which have been defined // The resulting set are the properties required but not added final Set<String> requiredProperties = new HashSet<>(model.getRequiredProperties()); requiredProperties.removeAll(existingKeys); if (!requiredProperties.isEmpty()) { annotationHolder.createWarningAnnotation(errorTarget, "Missing required properties on " + model.getId() + ": " + String.join(", ", requiredProperties)) .registerFix(new CreateMissingPropertiesIntentionAction(requiredProperties, mapping)); } } }