mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-14 18:05:27 +07:00
[json schema] support adding arbitrary metadata to schema elements and use that facility in amper for filtering of completion items
GitOrigin-RevId: 34d3641afeb2c2d3e6932d33f1e3c091b76e2205
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0b0fe86bfc
commit
c58f381025
@@ -190,7 +190,7 @@
|
||||
interface="com.jetbrains.jsonSchema.extension.JsonSchemaNestedCompletionsTreeProvider" dynamic="true"/>
|
||||
<extensionPoint qualifiedName="com.intellij.json.jsonSchemaEnabler" interface="com.jetbrains.jsonSchema.extension.JsonSchemaEnabler"
|
||||
dynamic="true"/>
|
||||
<extensionPoint qualifiedName="com.intellij.json.jsonSchemaCompletionHandlerProvider" interface="com.jetbrains.jsonSchema.extension.JsonSchemaCompletionHandlerProvider"
|
||||
<extensionPoint qualifiedName="com.intellij.json.jsonSchemaCompletionCustomizer" interface="com.jetbrains.jsonSchema.extension.JsonSchemaCompletionCustomizer"
|
||||
dynamic="true"/>
|
||||
<extensionPoint qualifiedName="com.intellij.json.jsonWidgetSuppressor"
|
||||
interface="com.jetbrains.jsonSchema.extension.JsonWidgetSuppressor" dynamic="true"/>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.jsonSchema.extension;
|
||||
|
||||
import com.intellij.codeInsight.completion.InsertHandler;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface JsonSchemaCompletionCustomizer {
|
||||
ExtensionPointName<JsonSchemaCompletionCustomizer> EXTENSION_POINT_NAME = ExtensionPointName.create("com.intellij.json.jsonSchemaCompletionCustomizer");
|
||||
|
||||
/**
|
||||
* Whether this customization is applicable to a file.
|
||||
* Normally there should be just one customizer per file, otherwise the behavior is not defined
|
||||
*/
|
||||
boolean isApplicable(@NotNull PsiFile file);
|
||||
|
||||
/**
|
||||
* Allows customizing insertion handler for enum values (e.g., to turn a value into a more complicated structure).
|
||||
* If it returns null, the default handler will be invoked.
|
||||
*/
|
||||
default @Nullable InsertHandler<LookupElement> createHandlerForEnumValue(JsonSchemaObject schema, String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to accept the completion item for a property
|
||||
*/
|
||||
default boolean acceptsPropertyCompletionItem(JsonSchemaObject parentSchema, String propertyName,
|
||||
@Nullable List<String> nestedPath,
|
||||
@NotNull PsiElement originalPosition) { return true; }
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.jsonSchema.extension;
|
||||
|
||||
import com.intellij.codeInsight.completion.InsertHandler;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface JsonSchemaCompletionHandlerProvider {
|
||||
ExtensionPointName<JsonSchemaCompletionHandlerProvider> EXTENSION_POINT_NAME = ExtensionPointName.create("com.intellij.json.jsonSchemaCompletionHandlerProvider");
|
||||
|
||||
default @Nullable InsertHandler<LookupElement> createHandlerForEnumValue(JsonSchemaObject schema, String value) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ import com.intellij.util.ThreeState
|
||||
import com.intellij.util.concurrency.ThreadingAssertions
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker
|
||||
import com.jetbrains.jsonSchema.extension.JsonSchemaCompletionHandlerProvider
|
||||
import com.jetbrains.jsonSchema.extension.JsonSchemaCompletionCustomizer
|
||||
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider
|
||||
import com.jetbrains.jsonSchema.extension.JsonSchemaNestedCompletionsTreeProvider.Companion.getNestedCompletionsData
|
||||
import com.jetbrains.jsonSchema.extension.SchemaType
|
||||
@@ -178,10 +178,13 @@ class JsonSchemaCompletionContributor : CompletionContributor() {
|
||||
adapter: JsonPropertyAdapter?,
|
||||
knownNames: MutableSet<String>,
|
||||
completionPath: SchemaPath?) {
|
||||
val customHandlers = JsonSchemaCompletionCustomizer.EXTENSION_POINT_NAME.extensionList
|
||||
.filter { it.isApplicable(originalPosition.containingFile) }
|
||||
StreamEx.of(schema.propertyNames)
|
||||
.filter { name -> !forbiddenNames.contains(name) && !knownNames.contains(name) || adapter != null && name == adapter.name }
|
||||
.forEach { name ->
|
||||
knownNames.add(name)
|
||||
if (customHandlers.size == 1 && !customHandlers[0].acceptsPropertyCompletionItem(schema, name, completionPath?.accessor(), originalPosition)) return@forEach
|
||||
val propertySchema = checkNotNull(schema.getPropertyByName(name))
|
||||
addPropertyVariant(name, propertySchema, completionPath, adapter?.nameValueAdapter)
|
||||
}
|
||||
@@ -194,7 +197,8 @@ class JsonSchemaCompletionContributor : CompletionContributor() {
|
||||
|
||||
if (schema.enum != null && completionPath == null) {
|
||||
// custom insert handlers are currently applicable only to enum values but can be extended later to cover more cases
|
||||
val customHandlers = JsonSchemaCompletionHandlerProvider.EXTENSION_POINT_NAME.extensionList
|
||||
val customHandlers = JsonSchemaCompletionCustomizer.EXTENSION_POINT_NAME.extensionList
|
||||
.filter { it.isApplicable(originalPosition.containingFile) }
|
||||
val metadata = schema.enumMetadata
|
||||
val isEnumOrderSensitive = schema.readChildNodeValue(X_INTELLIJ_ENUM_ORDER_SENSITIVE).toBoolean()
|
||||
val anEnum = schema.enum
|
||||
@@ -866,4 +870,9 @@ class JsonSchemaCompletionContributor : CompletionContributor() {
|
||||
codeStyleManager.reformatText(context.file, context.startOffset, context.tailOffset + offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class JsonSchemaMetadataEntry(
|
||||
val key: String,
|
||||
val values: List<String>
|
||||
)
|
||||
@@ -166,6 +166,10 @@ public abstract class JsonSchemaObject {
|
||||
|
||||
public abstract @Nullable JsonSchemaObject getSchemaDependencyByName(@NotNull String name);
|
||||
|
||||
// custom metadata provided by schemas, can be used in IDE features
|
||||
// the format in the schema is a key with either a single string value or an array of string values
|
||||
public abstract @Nullable List<JsonSchemaMetadataEntry> getMetadata();
|
||||
|
||||
// also remove?
|
||||
public abstract @Nullable List<? extends JsonSchemaObject> getAllOf();
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ public class JsonSchemaObjectImpl extends JsonSchemaObject {
|
||||
public @Nullable String myLanguageInjectionPrefix;
|
||||
public @Nullable String myLanguageInjectionPostfix;
|
||||
|
||||
public @Nullable List<JsonSchemaMetadataEntry> myMetadataEntries;
|
||||
|
||||
public @Nullable JsonSchemaType myType;
|
||||
public @Nullable Object myDefault;
|
||||
public @Nullable Map<String, Object> myExample;
|
||||
@@ -220,6 +222,15 @@ public class JsonSchemaObjectImpl extends JsonSchemaObject {
|
||||
return myRawFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<JsonSchemaMetadataEntry> getMetadata() {
|
||||
return myMetadataEntries;
|
||||
}
|
||||
|
||||
public void setMetadata(@Nullable List<JsonSchemaMetadataEntry> entries) {
|
||||
myMetadataEntries = entries;
|
||||
}
|
||||
|
||||
public void setLanguageInjection(@Nullable String injection) {
|
||||
myLanguageInjection = injection;
|
||||
}
|
||||
|
||||
@@ -193,6 +193,8 @@ public final class JsonSchemaReader {
|
||||
(element, object, queue, virtualFile) -> readInjectionMetadata(element, object));
|
||||
READERS_MAP.put(X_INTELLIJ_ENUM_METADATA,
|
||||
(element, object, queue, virtualFile) -> readEnumMetadata(element, object));
|
||||
READERS_MAP.put(X_INTELLIJ_METADATA,
|
||||
(element, object, queue, virtualFile) -> readCustomMetadata(element, object));
|
||||
READERS_MAP.put(X_INTELLIJ_CASE_INSENSITIVE, (element, object, queue, virtualFile) -> {
|
||||
if (element.isBooleanLiteral()) object.setForceCaseInsensitive(getBoolean(element));
|
||||
});
|
||||
@@ -259,6 +261,29 @@ public final class JsonSchemaReader {
|
||||
READERS_MAP.put("typeof", ((element, object, queue, virtualFile) -> object.setShouldValidateAgainstJSType(true)));
|
||||
}
|
||||
|
||||
private static void readCustomMetadata(JsonValueAdapter element, JsonSchemaObjectImpl object) {
|
||||
if (!(element instanceof JsonObjectValueAdapter)) return;
|
||||
List<JsonSchemaMetadataEntry> filters = new ArrayList<>();
|
||||
for (JsonPropertyAdapter adapter : ((JsonObjectValueAdapter)element).getPropertyList()) {
|
||||
String name = adapter.getName();
|
||||
if (name == null) continue;
|
||||
Collection<JsonValueAdapter> values = adapter.getValues();
|
||||
if (values.size() != 1) continue;
|
||||
JsonValueAdapter valueAdapter = values.iterator().next();
|
||||
if (valueAdapter.isStringLiteral()) {
|
||||
filters.add(new JsonSchemaMetadataEntry(name, Collections.singletonList(getString(valueAdapter))));
|
||||
}
|
||||
else if (valueAdapter.isArray()) {
|
||||
filters.add(new JsonSchemaMetadataEntry(name,
|
||||
Objects.requireNonNull(valueAdapter.getAsArray()).getElements().stream()
|
||||
.filter(v -> v.isStringLiteral())
|
||||
.map(v -> getString(v))
|
||||
.toList()));
|
||||
}
|
||||
}
|
||||
object.setMetadata(filters);
|
||||
}
|
||||
|
||||
private static void readEnumMetadata(JsonValueAdapter element, JsonSchemaObjectImpl object) {
|
||||
if (!(element instanceof JsonObjectValueAdapter)) return;
|
||||
Map<String, Map<String, String>> metadataMap = new HashMap<>();
|
||||
|
||||
@@ -85,7 +85,8 @@ public class LegacyJsonSchemaObjectMerger implements JsonSchemaObjectMerger {
|
||||
if (other.getMinProperties() != null) base.setMinProperties(other.getMinProperties());
|
||||
if (other.getEnum() != null) base.setEnum(other.getEnum());
|
||||
if (other.getNot() != null) base.setNot(other.getNot());
|
||||
if (other.getLanguageInjection() == null) base.setLanguageInjection(other.getLanguageInjection());
|
||||
if (other.getLanguageInjection() != null) base.setLanguageInjection(other.getLanguageInjection());
|
||||
if (other.getMetadata() != null) base.setMetadata(other.getMetadata());
|
||||
|
||||
//computed together because influence each other
|
||||
var mergedExclusionAndType = computeMergedExclusionAndType(base.getType(), other.getType(), other.getTypeVariants());
|
||||
|
||||
@@ -6,10 +6,7 @@ import com.intellij.util.asSafely
|
||||
import com.jetbrains.jsonSchema.extension.JsonSchemaValidation
|
||||
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter
|
||||
import com.jetbrains.jsonSchema.ide.JsonSchemaService
|
||||
import com.jetbrains.jsonSchema.impl.IfThenElse
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaObject
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaType
|
||||
import com.jetbrains.jsonSchema.impl.MergedJsonSchemaObject
|
||||
import com.jetbrains.jsonSchema.impl.*
|
||||
import com.jetbrains.jsonSchema.impl.light.legacy.LegacyJsonSchemaObjectMerger
|
||||
import com.jetbrains.jsonSchema.impl.light.versions.JsonSchemaInterpretationStrategy
|
||||
|
||||
@@ -121,6 +118,10 @@ internal class InheritedJsonSchemaObjectView(
|
||||
return LightweightJsonSchemaObjectMerger.mergeObjects(baseDef, otherDef, otherDef)
|
||||
}
|
||||
|
||||
override fun getMetadata(): MutableList<JsonSchemaMetadataEntry>? {
|
||||
return other.metadata
|
||||
}
|
||||
|
||||
override fun hasPatternProperties(): Boolean {
|
||||
return other.hasPatternProperties()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.jetbrains.jsonSchema.impl.light.nodes
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.keyFMap.KeyFMap
|
||||
@@ -495,6 +496,19 @@ abstract class JsonSchemaObjectBackedByJacksonBase(
|
||||
return JacksonSchemaNodeAccessor.readTextNodeValue(rawSchemaNode, X_INTELLIJ_HTML_DESCRIPTION)
|
||||
}
|
||||
|
||||
override fun getMetadata(): List<JsonSchemaMetadataEntry>? {
|
||||
return JacksonSchemaNodeAccessor.readNodeAsMapEntries(rawSchemaNode, X_INTELLIJ_METADATA)
|
||||
?.mapNotNull {
|
||||
val values = (it.second as? ArrayNode)?.let {
|
||||
it.elements().asSequence().mapNotNull {
|
||||
it.takeIf { it.isTextual }?.asText()
|
||||
}.toList()
|
||||
} ?: it.second.takeIf { it.isTextual }?.asText()?.let { listOf(it) }
|
||||
if (values.isNullOrEmpty()) null
|
||||
else JsonSchemaMetadataEntry(it.first, values)
|
||||
}?.toList()
|
||||
}
|
||||
|
||||
override fun getLanguageInjection(): String? {
|
||||
return JacksonSchemaNodeAccessor.readTextNodeValue(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION)
|
||||
?: JacksonSchemaNodeAccessor.readTextNodeValue(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION, LANGUAGE)
|
||||
|
||||
@@ -6,10 +6,7 @@ import com.intellij.util.asSafely
|
||||
import com.jetbrains.jsonSchema.extension.JsonSchemaValidation
|
||||
import com.jetbrains.jsonSchema.extension.adapters.JsonValueAdapter
|
||||
import com.jetbrains.jsonSchema.ide.JsonSchemaService
|
||||
import com.jetbrains.jsonSchema.impl.IfThenElse
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaObject
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaType
|
||||
import com.jetbrains.jsonSchema.impl.MergedJsonSchemaObject
|
||||
import com.jetbrains.jsonSchema.impl.*
|
||||
import com.jetbrains.jsonSchema.impl.light.legacy.LegacyJsonSchemaObjectMerger
|
||||
import com.jetbrains.jsonSchema.impl.light.versions.JsonSchemaInterpretationStrategy
|
||||
|
||||
@@ -125,6 +122,10 @@ internal class MergedJsonSchemaObjectView(
|
||||
return LightweightJsonSchemaObjectMerger.mergeObjects(baseDef, otherDef, otherDef)
|
||||
}
|
||||
|
||||
override fun getMetadata(): MutableList<JsonSchemaMetadataEntry>? {
|
||||
return other.metadata ?: base.metadata
|
||||
}
|
||||
|
||||
override fun hasPatternProperties(): Boolean {
|
||||
return booleanOr(JsonSchemaObject::hasPatternProperties)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.jetbrains.jsonSchema.impl.light.nodes
|
||||
import com.fasterxml.jackson.databind.node.MissingNode
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.jetbrains.jsonSchema.impl.IfThenElse
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaMetadataEntry
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaObject
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaType
|
||||
import com.jetbrains.jsonSchema.impl.light.SCHEMA_ROOT_POINTER
|
||||
@@ -265,6 +266,10 @@ internal object MissingJsonSchemaObject : JsonSchemaObjectBackedByJacksonBase(Mi
|
||||
throw UnsupportedOperationException(ERROR_MESSAGE)
|
||||
}
|
||||
|
||||
override fun getMetadata(): MutableList<JsonSchemaMetadataEntry>? {
|
||||
throw UnsupportedOperationException(ERROR_MESSAGE)
|
||||
}
|
||||
|
||||
override fun hasChildFieldsExcept(namesToSkip: Array<String>): Boolean {
|
||||
throw UnsupportedOperationException(ERROR_MESSAGE)
|
||||
}
|
||||
|
||||
@@ -69,5 +69,6 @@ const val X_INTELLIJ_LANGUAGE_INJECTION = "x-intellij-language-injection"
|
||||
const val X_INTELLIJ_CASE_INSENSITIVE = "x-intellij-case-insensitive"
|
||||
const val X_INTELLIJ_ENUM_METADATA = "x-intellij-enum-metadata"
|
||||
const val X_INTELLIJ_ENUM_ORDER_SENSITIVE = "x-intellij-enum-order-sensitive"
|
||||
const val X_INTELLIJ_METADATA = "x-intellij-metadata"
|
||||
|
||||
internal val ROOT_POINTER_VARIANTS = setOf(SCHEMA_ROOT_POINTER, "#/", "#", "")
|
||||
Reference in New Issue
Block a user