1+ // eslint-disable-next-line import/no-unresolved
2+ import { Edge , Graph , Node } from 'dotviz' ;
13import {
24 getNamedType ,
35 GraphQLField ,
46 GraphQLNamedType ,
5- GraphQLObjectType ,
67 isEnumType ,
78 isInputObjectType ,
89 isInterfaceType ,
@@ -21,179 +22,129 @@ import { stringifyTypeWrappers } from '../utils/stringify-type-wrappers.ts';
2122import { unreachable } from '../utils/unreachable.ts' ;
2223import { TypeGraph } from './type-graph.ts' ;
2324
24- export function getDot ( typeGraph : TypeGraph ) : string {
25+ export function getDot ( typeGraph : TypeGraph ) : Graph {
2526 const { schema } = typeGraph ;
2627
27- const nodeResults = [ ] ;
28- for ( const node of typeGraph . nodes . values ( ) ) {
29- nodeResults . push ( printNode ( node ) ) ;
30- }
31-
32- return `
33- digraph {
34- graph [
35- rankdir = "LR"
36- ];
37- node [
38- fontsize = "16"
39- fontname = "helvetica"
40- shape = "plaintext"
41- ];
42- edge [
43- ];
44- ranksep = 2.0
45- ${ nodeResults . join ( '\n' ) }
46- }
47- ` ;
48-
49- function printNode ( node : GraphQLNamedType ) {
50- return `
51- "${ node . name } " [
52- id = "${ typeObjToId ( node ) } "
53- label = ${ nodeLabel ( node ) }
54- ]
55- ${ forEachField ( ( id , field ) => {
56- if ( ! isNode ( getNamedType ( field . type ) ) ) {
57- return null ;
58- }
59- return `
60- "${ node . name } ":"${ field . name } " -> "${
61- getNamedType ( field . type ) . name
62- } " [
63- id = "${ id } => ${ typeObjToId ( getNamedType ( field . type ) ) } "
64- label = "${ node . name } :${ field . name } "
65- ]
66- ` ;
67- } ) } ;
68- ${ forEachPossibleTypes (
69- ( id , type ) => `
70- "${ node . name } ":"${ type . name } " -> "${ type . name } " [
71- id = "${ id } => ${ typeObjToId ( type ) } "
72- style = "dashed"
73- ]
74- ` ,
75- ) }
76- ${ forEachDerivedTypes (
77- ( id , type ) => `
78- "${ node . name } ":"${ type . name } " -> "${ type . name } " [
79- id = "${ id } => ${ typeObjToId ( type ) } "
80- style = "dotted"
81- ]
82- ` ,
83- ) }
84- ` ;
85-
86- function nodeLabel ( node : GraphQLNamedType ) : string {
87- const htmlID = HtmlId ( 'TYPE_TITLE::' + node . name ) ;
88- const kindLabel = isObjectType ( node )
89- ? ''
90- : '<<' + typeToKind ( node ) . toLowerCase ( ) + '>>' ;
91-
92- return `
93- <<TABLE ALIGN="LEFT" BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="5">
94- <TR>
95- <TD CELLPADDING="4" ${ htmlID } ><FONT POINT-SIZE="18">${
96- node . name
97- } </FONT><BR/>${ kindLabel } </TD>
98- </TR>
99- ${ nodeFields ( ) }
100- ${ possibleTypes ( ) }
101- ${ derivedTypes ( ) }
102- </TABLE>>
103- ` ;
104- }
105-
106- function isNode ( type : GraphQLNamedType ) : boolean {
107- return typeGraph . nodes . has ( type . name ) ;
108- }
109-
110- function nodeFields ( ) {
111- return forEachField ( ( id , field ) => {
112- const namedType = getNamedType ( field . type ) ;
113- if ( typeGraph . showLeafFields !== true && ! isNode ( namedType ) ) {
114- return null ;
115- }
116-
117- const parts = stringifyTypeWrappers ( field . type ) . map ( TEXT ) ;
118- const relayIcon = field . extensions . isRelayField ? TEXT ( '{R}' ) : '' ;
119- const deprecatedIcon =
120- field . deprecationReason != null ? TEXT ( '{D}' ) : '' ;
121- return `
122- <TR>
123- <TD ${ HtmlId ( id ) } ALIGN="LEFT" PORT="${ field . name } ">
124- <TABLE CELLPADDING="0" CELLSPACING="0" BORDER="0">
125- <TR>
126- <TD ALIGN="LEFT">${ field . name } <FONT> </FONT></TD>
127- <TD ALIGN="RIGHT">${ deprecatedIcon } ${ relayIcon } ${ parts [ 0 ] } ${
128- namedType . name
129- } ${ parts [ 1 ] } </TD>
130- </TR>
131- </TABLE>
132- </TD>
133- </TR>
134- ` ;
135- } ) ;
136- }
137-
138- function possibleTypes ( ) {
139- const possibleTypes = forEachPossibleTypes (
140- ( id , { name } ) => `
141- <TR>
142- <TD ${ HtmlId ( id ) } ALIGN="LEFT" PORT="${ name } ">${ name } </TD>
143- </TR>
144- ` ,
145- ) ;
146-
147- if ( possibleTypes === '' ) {
148- return '' ;
28+ const nodes : Array < Node > = [ ] ;
29+ const edges : Array < Edge > = [ ] ;
30+ for ( const type of typeGraph . nodes . values ( ) ) {
31+ const fields = mapFields < string > ( type , ( id , field ) => {
32+ const fieldType = getNamedType ( field . type ) ;
33+ if ( isNode ( fieldType ) ) {
34+ edges . push ( {
35+ tail : type . name ,
36+ head : fieldType . name ,
37+ attributes : {
38+ tailport : field . name ,
39+ id : `${ id } => ${ typeObjToId ( fieldType ) } ` ,
40+ label : `${ type . name } :${ field . name } ` ,
41+ } ,
42+ } ) ;
43+ return fieldLabel ( id , field ) ;
14944 }
150-
45+ return typeGraph . showLeafFields ? fieldLabel ( id , field ) : '' ;
46+ } ) . join ( '' ) ;
47+
48+ const possibleTypes = mapPossibleTypes < string > ( type , ( id , possibleType ) => {
49+ edges . push ( {
50+ tail : type . name ,
51+ head : possibleType . name ,
52+ attributes : {
53+ tailport : possibleType . name ,
54+ id : `${ id } => ${ typeObjToId ( possibleType ) } ` ,
55+ style : 'dashed' ,
56+ } ,
57+ } ) ;
15158 return `
15259 <TR>
153- <TD>possible types </TD>
60+ <TD ${ HtmlId ( id ) } ALIGN="LEFT" PORT=" ${ possibleType . name } "> ${ possibleType . name } </TD>
15461 </TR>
155- ${ possibleTypes }
15662 ` ;
157- }
158-
159- function derivedTypes ( ) {
160- const implementations = forEachDerivedTypes (
161- ( id , { name } ) => `
63+ } ) . join ( '' ) ;
64+
65+ const derivedTypes = mapDerivedTypes < string > (
66+ schema ,
67+ type ,
68+ ( id , derivedType ) => {
69+ edges . push ( {
70+ tail : type . name ,
71+ head : derivedType . name ,
72+ attributes : {
73+ tailport : derivedType . name ,
74+ id : `${ id } => ${ typeObjToId ( derivedType ) } ` ,
75+ style : 'dotted' ,
76+ } ,
77+ } ) ;
78+ return `
16279 <TR>
163- <TD ${ HtmlId ( id ) } ALIGN="LEFT" PORT="${ name } ">${ name } </TD>
80+ <TD ${ HtmlId ( id ) } ALIGN="LEFT" PORT="${ derivedType . name } ">${ derivedType . name } </TD>
16481 </TR>
165- ` ,
166- ) ;
82+ ` ;
83+ } ,
84+ ) . join ( '' ) ;
16785
168- if ( implementations === '' ) {
169- return '' ;
170- }
86+ const htmlID = HtmlId ( 'TYPE_TITLE::' + type . name ) ;
87+ const kindLabel = isObjectType ( type )
88+ ? ''
89+ : '<<' + typeToKind ( type ) . toLowerCase ( ) + '>>' ;
17190
172- return `
91+ const html = `
92+ <TABLE ALIGN="LEFT" BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="5">
17393 <TR>
174- <TD>implementations </TD>
94+ <TD CELLPADDING="4" ${ htmlID } ><FONT POINT-SIZE="18"> ${ type . name } </FONT><BR/> ${ kindLabel } </TD>
17595 </TR>
176- ${ implementations }
177- ` ;
178- }
179-
180- function forEachField (
181- stringify : ( id : string , field : GraphQLField < any , any > ) => string | null ,
182- ) : string {
183- return mapFields ( node , stringify ) . join ( '\n' ) ;
184- }
96+ ${ fields }
97+ ${ possibleTypes !== '' ? '<TR><TD>possible types</TD></TR>\n' + possibleTypes : '' }
98+ ${ derivedTypes !== '' ? '<TR><TD>implementations</TD></TR>\n' + derivedTypes : '' }
99+ </TABLE>
100+ ` ;
101+ nodes . push ( {
102+ name : type . name ,
103+ attributes : {
104+ id : typeObjToId ( type ) ,
105+ label : { html } ,
106+ } ,
107+ } ) ;
108+ }
185109
186- function forEachPossibleTypes (
187- stringify : ( id : string , type : GraphQLObjectType ) => string | null ,
188- ) : string {
189- return mapPossibleTypes ( node , stringify ) . join ( '\n' ) ;
190- }
110+ return {
111+ directed : true ,
112+ graphAttributes : {
113+ rankdir : 'LR' ,
114+ ranksep : 2.0 ,
115+ } ,
116+ nodeAttributes : {
117+ fontsize : '16' ,
118+ fontname : 'helvetica' ,
119+ shape : 'plaintext' ,
120+ } ,
121+ nodes,
122+ edges,
123+ } ;
124+
125+ function isNode ( type : GraphQLNamedType ) : boolean {
126+ return typeGraph . nodes . has ( type . name ) ;
127+ }
191128
192- function forEachDerivedTypes (
193- stringify : ( id : string , type : GraphQLNamedType ) => string | null ,
194- ) {
195- return mapDerivedTypes ( schema , node , stringify ) . join ( '\n' ) ;
196- }
129+ function fieldLabel ( id : string , field : GraphQLField < any , any > ) : string {
130+ const namedType = getNamedType ( field . type ) ;
131+ const parts = stringifyTypeWrappers ( field . type ) . map ( TEXT ) ;
132+ const relayIcon = field . extensions . isRelayField ? TEXT ( '{R}' ) : '' ;
133+ const deprecatedIcon = field . deprecationReason != null ? TEXT ( '{D}' ) : '' ;
134+ return `
135+ <TR>
136+ <TD ${ HtmlId ( id ) } ALIGN="LEFT" PORT="${ field . name } ">
137+ <TABLE CELLPADDING="0" CELLSPACING="0" BORDER="0">
138+ <TR>
139+ <TD ALIGN="LEFT">${ field . name } <FONT> </FONT></TD>
140+ <TD ALIGN="RIGHT">${ deprecatedIcon } ${ relayIcon } ${ parts [ 0 ] } ${
141+ namedType . name
142+ } ${ parts [ 1 ] } </TD>
143+ </TR>
144+ </TABLE>
145+ </TD>
146+ </TR>
147+ ` ;
197148 }
198149}
199150
0 commit comments