@@ -20,7 +20,11 @@ export default function SchemaViewer(props: SchemaViewerProps): JSX.Element {
2020 const rootSchemaInfo = describeSchema ( props ) ;
2121 const { id, rootSchema, yaml : _yaml , pointer } = rootSchemaInfo ;
2222
23- const transformedSchema = transformSchema ( rootSchema , id || "" ) ;
23+ // the schema this page shows is outermost in the dynamic scope, so its
24+ // dynamic anchors take precedence over those of any schema it references
25+ const pageAnchors = dynamicAnchors ( rootSchema ) ;
26+
27+ const transformedSchema = transformSchema ( rootSchema , id || "" , pageAnchors ) ;
2428
2529 return (
2630 < Tabs >
@@ -39,10 +43,13 @@ export default function SchemaViewer(props: SchemaViewerProps): JSX.Element {
3943 schema : {
4044 resolve : ( uri : URL ) => {
4145 const id = uri . toString ( ) ;
42- const { schema } = describeSchema ( {
46+ const { schema, rootSchema } = describeSchema ( {
4347 schema : { id } ,
4448 } ) ;
45- return transformSchema ( schema , id ) ;
49+ return transformSchema ( schema , id , {
50+ ...dynamicAnchors ( rootSchema ) ,
51+ ...pageAnchors ,
52+ } ) ;
4653 } ,
4754 } ,
4855 } ,
@@ -97,8 +104,81 @@ export default function SchemaViewer(props: SchemaViewerProps): JSX.Element {
97104 ) ;
98105}
99106
100- function transformSchema ( schema : JSONSchema , id : string ) : JSONSchema {
101- return insertIds ( ensureRefsLackSiblings ( schema ) , `${ id } #` ) ;
107+ type DynamicAnchors = { [ name : string ] : JSONSchema } ;
108+
109+ function transformSchema (
110+ schema : JSONSchema ,
111+ id : string ,
112+ anchors : DynamicAnchors ,
113+ ) : JSONSchema {
114+ return insertIds (
115+ ensureRefsLackSiblings ( resolveDynamicRefs ( schema , anchors ) ) ,
116+ `${ id } #` ,
117+ ) ;
118+ }
119+
120+ // collects the schemas in a root schema's `$defs` that declare a
121+ // `$dynamicAnchor`, keyed by anchor name
122+ function dynamicAnchors ( rootSchema : JSONSchema ) : DynamicAnchors {
123+ const anchors : DynamicAnchors = { } ;
124+
125+ const definitions =
126+ ( typeof rootSchema === "object" && rootSchema . $defs ) || { } ;
127+
128+ for ( const definition of Object . values ( definitions ) ) {
129+ if ( typeof definition !== "object" ) {
130+ continue ;
131+ }
132+
133+ const { $dynamicAnchor, ...rest } = definition as {
134+ $dynamicAnchor ?: string ;
135+ } ;
136+
137+ if ( typeof $dynamicAnchor === "string" ) {
138+ anchors [ $dynamicAnchor ] = rest as JSONSchema ;
139+ }
140+ }
141+
142+ return anchors ;
143+ }
144+
145+ // recursively replaces each `{ $dynamicRef: "#name" }` with the schema
146+ // that the dynamic anchor `name` resolves to.
147+ //
148+ // docusaurus-json-schema-plugin reports any `$dynamicRef` as an unresolved
149+ // reference, so this integration resolves them itself. The caller supplies
150+ // the anchors in dynamic-scope order: those of the page's own schema
151+ // override those of a schema that the page references.
152+ function resolveDynamicRefs < T > ( obj : T , anchors : DynamicAnchors ) : T {
153+ if ( ! obj || typeof obj !== "object" ) {
154+ return obj ;
155+ }
156+
157+ if ( Array . isArray ( obj ) ) {
158+ return obj . map ( ( item ) => resolveDynamicRefs ( item , anchors ) ) as T ;
159+ }
160+
161+ const { $dynamicRef, ...rest } = obj as T & object & { $dynamicRef ?: string } ;
162+
163+ const result = Object . entries ( rest ) . reduce ( ( newObj , [ key , value ] ) => {
164+ // @ts -expect-error dynamic key assignment
165+ newObj [ key ] = resolveDynamicRefs ( value , anchors ) ;
166+ return newObj ;
167+ } , { } as T ) ;
168+
169+ if ( $dynamicRef === undefined ) {
170+ return result ;
171+ }
172+
173+ const anchor = $dynamicRef . startsWith ( "#" )
174+ ? anchors [ $dynamicRef . slice ( 1 ) ]
175+ : undefined ;
176+
177+ if ( ! anchor ) {
178+ throw new Error ( `Could not resolve $dynamicRef "${ $dynamicRef } "` ) ;
179+ }
180+
181+ return { ...anchor , ...result } ;
102182}
103183
104184function insertIds < T > ( obj : T , rootId : string ) : T {
0 commit comments