From 2465a29bc671c354add48d1ea0f82210a7549fd3 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Thu, 11 Apr 2024 01:11:58 -0400 Subject: [PATCH] Improve schema extension rendering - Link directly to schemas with doc section links in index - Ignore matching `type`s when determining if `type` is included for semantic purposes in an extension schema - Cleanup imports and define a handy type for schemas with internal IDs --- packages/web/src/contexts/SchemaContext.tsx | 11 +++ .../components/CreateNodes.tsx | 11 +-- .../components/UnnecessaryComposition.tsx | 69 ++++++++++++++++--- 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/packages/web/src/contexts/SchemaContext.tsx b/packages/web/src/contexts/SchemaContext.tsx index 9e0996f7..cb73369b 100644 --- a/packages/web/src/contexts/SchemaContext.tsx +++ b/packages/web/src/contexts/SchemaContext.tsx @@ -1,7 +1,18 @@ import { useContext, createContext } from "react"; +import type { JSONSchema } from "json-schema-typed/draft-2020-12"; + import type { SchemaInfo } from "@ethdebug/format"; import type { SchemaIndex } from "@site/src/schemas"; +export type JSONSchemaWithInternalIdKeys = + | boolean + | ( + & Exclude + & { + [internalIdKey]: string; + } + ); + export interface SchemaContextValue { rootSchemaInfo?: SchemaInfo; schemaIndex: SchemaIndex; diff --git a/packages/web/src/theme/JSONSchemaViewer/components/CreateNodes.tsx b/packages/web/src/theme/JSONSchemaViewer/components/CreateNodes.tsx index abd27aa8..75663b44 100644 --- a/packages/web/src/theme/JSONSchemaViewer/components/CreateNodes.tsx +++ b/packages/web/src/theme/JSONSchemaViewer/components/CreateNodes.tsx @@ -1,9 +1,12 @@ import React from 'react'; import CreateTypes from "@theme-original/JSONSchemaViewer/components/CreateTypes"; import CreateNodes from '@theme-original/JSONSchemaViewer/components/CreateNodes'; -import type { JSONSchema } from "json-schema-typed/draft-2020-12"; import { useSchemaHierarchyContext } from "@theme-original/JSONSchemaViewer/contexts"; -import { useSchemaContext, internalIdKey } from "@site/src/contexts/SchemaContext"; +import { + useSchemaContext, + internalIdKey, + type JSONSchemaWithInternalIdKeys as JSONSchema +} from "@site/src/contexts/SchemaContext"; import Link from "@docusaurus/Link"; import UnnecessaryCompositionSchema, { @@ -11,9 +14,7 @@ import UnnecessaryCompositionSchema, { } from "./UnnecessaryComposition"; export default function CreateNodesWrapper(props: { - schema: Exclude & { - [internalIdKey]: string - } + schema: Exclude }) { const { level } = useSchemaHierarchyContext(); const { schemaIndex } = useSchemaContext(); diff --git a/packages/web/src/theme/JSONSchemaViewer/components/UnnecessaryComposition.tsx b/packages/web/src/theme/JSONSchemaViewer/components/UnnecessaryComposition.tsx index dd20f6fd..e95013a3 100644 --- a/packages/web/src/theme/JSONSchemaViewer/components/UnnecessaryComposition.tsx +++ b/packages/web/src/theme/JSONSchemaViewer/components/UnnecessaryComposition.tsx @@ -1,22 +1,27 @@ import React from "react"; -import type { JSONSchema } from "json-schema-typed/draft-2020-12"; +import Link from "@docusaurus/Link"; import CreateNodes from "@theme/JSONSchemaViewer/components/CreateNodes"; import CreateEdge from "@theme-original/JSONSchemaViewer/components/CreateEdge"; import { SchemaHierarchyComponent } from "@theme-original/JSONSchemaViewer/contexts" import { Collapsible } from "@theme/JSONSchemaViewer/components"; import { GenerateFriendlyName, QualifierMessages } from "@theme/JSONSchemaViewer/utils"; -import { internalIdKey } from "@site/src/contexts/SchemaContext"; +import { + useSchemaContext, + internalIdKey, + type JSONSchemaWithInternalIdKeys +} from "@site/src/contexts/SchemaContext"; +import type { JSONSchema } from "json-schema-typed/draft-2020-12"; import { CreateDescription } from "@theme/JSONSchemaViewer/JSONSchemaElements"; import { useJSVOptionsContext } from "@theme/JSONSchemaViewer/contexts" export interface UnnecessaryComposition { - schemaWithoutUnnecessaryComposition: Exclude; + schemaWithoutUnnecessaryComposition: Exclude; unnecessaryCompositionKeyword: "allOf" | "oneOf" | "anyOf"; - unnecessarilyComposedSchema: JSONSchema; + unnecessarilyComposedSchema: JSONSchemaWithInternalIdKeys; } export function detectUnnecessaryComposition( - schema: JSONSchema + schema: JSONSchemaWithInternalIdKeys ): UnnecessaryComposition | undefined { if (typeof schema === "boolean") { return; @@ -37,7 +42,7 @@ export function detectUnnecessaryComposition( } = schema; const [unnecessarilyComposedSchema] = - composition as [JSONSchema] /* (we know this from filter above) */; + composition as [JSONSchemaWithInternalIdKeys] /* (we know this from filter above) */; return { unnecessarilyComposedSchema, @@ -55,6 +60,7 @@ export default function UnnecessaryCompositionSchema({ unnecessarilyComposedSchema }: UnnecessaryCompositionSchemaProps): JSX.Element { const jsvOptions = useJSVOptionsContext(); + const { schemaIndex } = useSchemaContext(); // treat the unnecessary composition to represent the extension of a base // schema, where the unnecessarily composed schema is the base @@ -65,7 +71,31 @@ export default function UnnecessaryCompositionSchema({ semantics } = separateDocumentationFromSemantics(extensionSchema); - if (Object.keys(semantics).length === 0) { + // due to a limitation of docusaurus-json-schema-plugin, use of the `type` + // field is necessary in order for the plugin to display schema descriptions. + // + // for a given extension schema, check whether the use of the `type` field + // is actually a semantic difference vs. the base schema + const onlyExtendsDocumentation = Object.keys(semantics).length === 0 || ( + Object.keys(semantics).length === 1 && + "type" in semantics && + typeof baseSchema === "object" && + "type" in baseSchema && + (( + typeof semantics.type === "string" && + semantics.type === baseSchema.type + ) || ( + semantics.type instanceof Array && + baseSchema.type instanceof Array && + semantics.type.length === baseSchema.type.length && + (semantics.type as string[]).every( + type_ => + (baseSchema.type as string[]).includes(type_) + ) + )) + ); + + if (onlyExtendsDocumentation) { const { description } = documentation; return <> @@ -81,6 +111,29 @@ export default function UnnecessaryCompositionSchema({ ; } + const { [internalIdKey]: id } = baseSchema; + + if (id && id in schemaIndex) { + const { + href, + title = `${ + id.startsWith("schema:") + ? id.slice("schema:".length) + : id + } schema` + } = schemaIndex[id as keyof typeof schemaIndex]; + + return ( + <> + extensions  + This schema extends the {title}. +

+ +

+ + ); + } + return ( <> extensions  @@ -113,7 +166,7 @@ export default function UnnecessaryCompositionSchema({ function separateDocumentationFromSemantics(schema: JSONSchema): { documentation: Exclude, - semantics: JSONSchema + semantics: Exclude } { if (typeof schema === "boolean") { return {