Skip to content

Commit 32a5492

Browse files
committed
Tests for visitWithTypeInfo, support for editing
1 parent bd87fd3 commit 32a5492

File tree

2 files changed

+206
-7
lines changed

2 files changed

+206
-7
lines changed

src/language/__tests__/visitor.js

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
import { expect } from 'chai';
1111
import { describe, it } from 'mocha';
1212
import { parse } from '../parser';
13+
import { print } from '../printer';
1314
import { readFileSync } from 'fs';
14-
import { visit, visitInParallel, BREAK } from '../visitor';
15+
import { visit, visitInParallel, visitWithTypeInfo, BREAK } from '../visitor';
1516
import { join } from 'path';
17+
import { TypeInfo } from '../../utilities/TypeInfo';
18+
import { testSchema } from '../../validation/__tests__/harness';
19+
import { getNamedType, isCompositeType } from '../../type';
1620

1721

1822
describe('Visitor', () => {
@@ -975,4 +979,197 @@ describe('Visitor', () => {
975979

976980
});
977981

982+
describe('visitWithTypeInfo', () => {
983+
984+
it('maintains type info during visit', () => {
985+
const visited = [];
986+
987+
const typeInfo = new TypeInfo(testSchema);
988+
989+
const ast = parse('{ human(id: 4) { name, pets { name }, unknown } }');
990+
visit(ast, visitWithTypeInfo(typeInfo, {
991+
enter(node) {
992+
let parentType = typeInfo.getParentType();
993+
let type = typeInfo.getType();
994+
let inputType = typeInfo.getInputType();
995+
visited.push([
996+
'enter',
997+
node.kind,
998+
node.kind === 'Name' ? node.value : null,
999+
parentType ? String(parentType) : null,
1000+
type ? String(type) : null,
1001+
inputType ? String(inputType) : null
1002+
]);
1003+
},
1004+
leave(node) {
1005+
let parentType = typeInfo.getParentType();
1006+
let type = typeInfo.getType();
1007+
let inputType = typeInfo.getInputType();
1008+
visited.push([
1009+
'leave',
1010+
node.kind,
1011+
node.kind === 'Name' ? node.value : null,
1012+
parentType ? String(parentType) : null,
1013+
type ? String(type) : null,
1014+
inputType ? String(inputType) : null
1015+
]);
1016+
}
1017+
}));
1018+
1019+
expect(visited).to.deep.equal([
1020+
[ 'enter', 'Document', null, null, null, null ],
1021+
[ 'enter', 'OperationDefinition', null, null, 'QueryRoot', null ],
1022+
[ 'enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null ],
1023+
[ 'enter', 'Field', null, 'QueryRoot', 'Human', null ],
1024+
[ 'enter', 'Name', 'human', 'QueryRoot', 'Human', null ],
1025+
[ 'leave', 'Name', 'human', 'QueryRoot', 'Human', null ],
1026+
[ 'enter', 'Argument', null, 'QueryRoot', 'Human', 'ID' ],
1027+
[ 'enter', 'Name', 'id', 'QueryRoot', 'Human', 'ID' ],
1028+
[ 'leave', 'Name', 'id', 'QueryRoot', 'Human', 'ID' ],
1029+
[ 'enter', 'IntValue', null, 'QueryRoot', 'Human', 'ID' ],
1030+
[ 'leave', 'IntValue', null, 'QueryRoot', 'Human', 'ID' ],
1031+
[ 'leave', 'Argument', null, 'QueryRoot', 'Human', 'ID' ],
1032+
[ 'enter', 'SelectionSet', null, 'Human', 'Human', null ],
1033+
[ 'enter', 'Field', null, 'Human', 'String', null ],
1034+
[ 'enter', 'Name', 'name', 'Human', 'String', null ],
1035+
[ 'leave', 'Name', 'name', 'Human', 'String', null ],
1036+
[ 'leave', 'Field', null, 'Human', 'String', null ],
1037+
[ 'enter', 'Field', null, 'Human', '[Pet]', null ],
1038+
[ 'enter', 'Name', 'pets', 'Human', '[Pet]', null ],
1039+
[ 'leave', 'Name', 'pets', 'Human', '[Pet]', null ],
1040+
[ 'enter', 'SelectionSet', null, 'Pet', '[Pet]', null ],
1041+
[ 'enter', 'Field', null, 'Pet', 'String', null ],
1042+
[ 'enter', 'Name', 'name', 'Pet', 'String', null ],
1043+
[ 'leave', 'Name', 'name', 'Pet', 'String', null ],
1044+
[ 'leave', 'Field', null, 'Pet', 'String', null ],
1045+
[ 'leave', 'SelectionSet', null, 'Pet', '[Pet]', null ],
1046+
[ 'leave', 'Field', null, 'Human', '[Pet]', null ],
1047+
[ 'enter', 'Field', null, 'Human', null, null ],
1048+
[ 'enter', 'Name', 'unknown', 'Human', null, null ],
1049+
[ 'leave', 'Name', 'unknown', 'Human', null, null ],
1050+
[ 'leave', 'Field', null, 'Human', null, null ],
1051+
[ 'leave', 'SelectionSet', null, 'Human', 'Human', null ],
1052+
[ 'leave', 'Field', null, 'QueryRoot', 'Human', null ],
1053+
[ 'leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null ],
1054+
[ 'leave', 'OperationDefinition', null, null, 'QueryRoot', null ],
1055+
[ 'leave', 'Document', null, null, null, null ]
1056+
]);
1057+
});
1058+
1059+
it('maintains type info during edit', () => {
1060+
const visited = [];
1061+
const typeInfo = new TypeInfo(testSchema);
1062+
1063+
const ast = parse(
1064+
'{ human(id: 4) { name, pets }, alien }'
1065+
);
1066+
const editedAst = visit(ast, visitWithTypeInfo(typeInfo, {
1067+
enter(node) {
1068+
let parentType = typeInfo.getParentType();
1069+
let type = typeInfo.getType();
1070+
let inputType = typeInfo.getInputType();
1071+
visited.push([
1072+
'enter',
1073+
node.kind,
1074+
node.kind === 'Name' ? node.value : null,
1075+
parentType ? String(parentType) : null,
1076+
type ? String(type) : null,
1077+
inputType ? String(inputType) : null
1078+
]);
1079+
1080+
// Make a query valid by adding missing selection sets.
1081+
if (
1082+
node.kind === 'Field' &&
1083+
!node.selectionSet &&
1084+
isCompositeType(getNamedType(type))
1085+
) {
1086+
return {
1087+
kind: 'Field',
1088+
alias: node.alias,
1089+
name: node.name,
1090+
arguments: node.arguments,
1091+
directives: node.directives,
1092+
selectionSet: {
1093+
kind: 'SelectionSet',
1094+
selections: [
1095+
{
1096+
kind: 'Field',
1097+
name: { kind: 'Name', value: '__typename' }
1098+
}
1099+
]
1100+
}
1101+
};
1102+
}
1103+
},
1104+
leave(node) {
1105+
let parentType = typeInfo.getParentType();
1106+
let type = typeInfo.getType();
1107+
let inputType = typeInfo.getInputType();
1108+
visited.push([
1109+
'leave',
1110+
node.kind,
1111+
node.kind === 'Name' ? node.value : null,
1112+
parentType ? String(parentType) : null,
1113+
type ? String(type) : null,
1114+
inputType ? String(inputType) : null
1115+
]);
1116+
}
1117+
}));
1118+
1119+
expect(print(ast)).to.deep.equal(print(parse(
1120+
'{ human(id: 4) { name, pets }, alien }'
1121+
)));
1122+
1123+
expect(print(editedAst)).to.deep.equal(print(parse(
1124+
'{ human(id: 4) { name, pets { __typename } }, alien { __typename } }'
1125+
)));
1126+
1127+
expect(visited).to.deep.equal([
1128+
[ 'enter', 'Document', null, null, null, null ],
1129+
[ 'enter', 'OperationDefinition', null, null, 'QueryRoot', null ],
1130+
[ 'enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null ],
1131+
[ 'enter', 'Field', null, 'QueryRoot', 'Human', null ],
1132+
[ 'enter', 'Name', 'human', 'QueryRoot', 'Human', null ],
1133+
[ 'leave', 'Name', 'human', 'QueryRoot', 'Human', null ],
1134+
[ 'enter', 'Argument', null, 'QueryRoot', 'Human', 'ID' ],
1135+
[ 'enter', 'Name', 'id', 'QueryRoot', 'Human', 'ID' ],
1136+
[ 'leave', 'Name', 'id', 'QueryRoot', 'Human', 'ID' ],
1137+
[ 'enter', 'IntValue', null, 'QueryRoot', 'Human', 'ID' ],
1138+
[ 'leave', 'IntValue', null, 'QueryRoot', 'Human', 'ID' ],
1139+
[ 'leave', 'Argument', null, 'QueryRoot', 'Human', 'ID' ],
1140+
[ 'enter', 'SelectionSet', null, 'Human', 'Human', null ],
1141+
[ 'enter', 'Field', null, 'Human', 'String', null ],
1142+
[ 'enter', 'Name', 'name', 'Human', 'String', null ],
1143+
[ 'leave', 'Name', 'name', 'Human', 'String', null ],
1144+
[ 'leave', 'Field', null, 'Human', 'String', null ],
1145+
[ 'enter', 'Field', null, 'Human', '[Pet]', null ],
1146+
[ 'enter', 'Name', 'pets', 'Human', '[Pet]', null ],
1147+
[ 'leave', 'Name', 'pets', 'Human', '[Pet]', null ],
1148+
[ 'enter', 'SelectionSet', null, 'Pet', '[Pet]', null ],
1149+
[ 'enter', 'Field', null, 'Pet', 'String!', null ],
1150+
[ 'enter', 'Name', '__typename', 'Pet', 'String!', null ],
1151+
[ 'leave', 'Name', '__typename', 'Pet', 'String!', null ],
1152+
[ 'leave', 'Field', null, 'Pet', 'String!', null ],
1153+
[ 'leave', 'SelectionSet', null, 'Pet', '[Pet]', null ],
1154+
[ 'leave', 'Field', null, 'Human', '[Pet]', null ],
1155+
[ 'leave', 'SelectionSet', null, 'Human', 'Human', null ],
1156+
[ 'leave', 'Field', null, 'QueryRoot', 'Human', null ],
1157+
[ 'enter', 'Field', null, 'QueryRoot', 'Alien', null ],
1158+
[ 'enter', 'Name', 'alien', 'QueryRoot', 'Alien', null ],
1159+
[ 'leave', 'Name', 'alien', 'QueryRoot', 'Alien', null ],
1160+
[ 'enter', 'SelectionSet', null, 'Alien', 'Alien', null ],
1161+
[ 'enter', 'Field', null, 'Alien', 'String!', null ],
1162+
[ 'enter', 'Name', '__typename', 'Alien', 'String!', null ],
1163+
[ 'leave', 'Name', '__typename', 'Alien', 'String!', null ],
1164+
[ 'leave', 'Field', null, 'Alien', 'String!', null ],
1165+
[ 'leave', 'SelectionSet', null, 'Alien', 'Alien', null ],
1166+
[ 'leave', 'Field', null, 'QueryRoot', 'Alien', null ],
1167+
[ 'leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null ],
1168+
[ 'leave', 'OperationDefinition', null, null, 'QueryRoot', null ],
1169+
[ 'leave', 'Document', null, null, null, null ]
1170+
]);
1171+
});
1172+
1173+
});
1174+
9781175
});

src/language/visitor.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,6 @@ export function visitInParallel(visitors) {
315315
/**
316316
* Creates a new visitor instance which maintains a provided TypeInfo instance
317317
* along with visiting visitor.
318-
*
319-
* Visitors must not directly modify the AST nodes and only returning false to
320-
* skip sub-branches is supported.
321318
*/
322319
export function visitWithTypeInfo(typeInfo, visitor) {
323320
return {
@@ -326,18 +323,23 @@ export function visitWithTypeInfo(typeInfo, visitor) {
326323
const fn = getVisitFn(visitor, node.kind, /* isLeaving */ false);
327324
if (fn) {
328325
const result = fn.apply(visitor, arguments);
329-
if (result === false) {
326+
if (result !== undefined) {
330327
typeInfo.leave(node);
331-
return false;
328+
if (isNode(result)) {
329+
typeInfo.enter(result);
330+
}
332331
}
332+
return result;
333333
}
334334
},
335335
leave(node) {
336336
const fn = getVisitFn(visitor, node.kind, /* isLeaving */ true);
337+
let result;
337338
if (fn) {
338-
fn.apply(visitor, arguments);
339+
result = fn.apply(visitor, arguments);
339340
}
340341
typeInfo.leave(node);
342+
return result;
341343
}
342344
};
343345
}

0 commit comments

Comments
 (0)