Skip to content

Commit 3aa74aa

Browse files
committed
switch to use dotviz json format
1 parent d66b672 commit 3aa74aa

File tree

3 files changed

+113
-161
lines changed

3 files changed

+113
-161
lines changed

cspell.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ overrides:
2020
- requestfailed
2121

2222
words:
23+
- tailport
2324
- dotviz
2425
- graphviz
2526
- svgr

src/graph/dot.ts

Lines changed: 110 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
// eslint-disable-next-line import/no-unresolved
2+
import { Edge, Graph, Node } from 'dotviz';
13
import {
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';
2122
import { unreachable } from '../utils/unreachable.ts';
2223
import { 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+
: '&lt;&lt;' + typeToKind(type).toLowerCase() + '&gt;&gt;';
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

src/graph/graphviz-worker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ export class VizWorker {
9191
console.timeEnd('graphql-voyager: Rendering SVG');
9292
if (result.errors.length !== 0) {
9393
return reject(
94-
AggregateError([
94+
new AggregateError(
9595
result.errors.map(
9696
(error) => new Error(`${error.level} : ${error.message}`),
9797
),
98-
]),
98+
),
9999
);
100100
}
101101
if (result.status === 'success') {

0 commit comments

Comments
 (0)