Skip to content

Commit 62a08fa

Browse files
Replace all instances of ObjMap<boolean> with ES6 Set (#3688)
Motivation: I was inspecting code for unrelated reason and just spotted this pattern. I think it worth to refactor it since it easier to understand intention looking on code that uses Set's has/add methods. Benchmark shows no measurable changes in CPU/memory usages.
1 parent 9df9079 commit 62a08fa

9 files changed

+91
-125
lines changed

src/type/schema.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ export class GraphQLSchema {
146146
private _subscriptionType: Maybe<GraphQLObjectType>;
147147
private _directives: ReadonlyArray<GraphQLDirective>;
148148
private _typeMap: TypeMap;
149-
private _subTypeMap: ObjMap<ObjMap<boolean>>;
149+
private _subTypeMap: Map<
150+
GraphQLAbstractType,
151+
Set<GraphQLObjectType | GraphQLInterfaceType>
152+
>;
153+
150154
private _implementationsMap: ObjMap<{
151155
objects: Array<GraphQLObjectType>;
152156
interfaces: Array<GraphQLInterfaceType>;
@@ -202,7 +206,7 @@ export class GraphQLSchema {
202206

203207
// Storing the resulting map for reference by the schema.
204208
this._typeMap = Object.create(null);
205-
this._subTypeMap = Object.create(null);
209+
this._subTypeMap = new Map();
206210
// Keep track of all implementations by interface name.
207211
this._implementationsMap = Object.create(null);
208212

@@ -308,27 +312,21 @@ export class GraphQLSchema {
308312
abstractType: GraphQLAbstractType,
309313
maybeSubType: GraphQLObjectType | GraphQLInterfaceType,
310314
): boolean {
311-
let map = this._subTypeMap[abstractType.name];
312-
if (map === undefined) {
313-
map = Object.create(null);
314-
315+
let set = this._subTypeMap.get(abstractType);
316+
if (set === undefined) {
315317
if (isUnionType(abstractType)) {
316-
for (const type of abstractType.getTypes()) {
317-
map[type.name] = true;
318-
}
318+
set = new Set<GraphQLObjectType>(abstractType.getTypes());
319319
} else {
320320
const implementations = this.getImplementations(abstractType);
321-
for (const type of implementations.objects) {
322-
map[type.name] = true;
323-
}
324-
for (const type of implementations.interfaces) {
325-
map[type.name] = true;
326-
}
321+
set = new Set<GraphQLObjectType | GraphQLInterfaceType>([
322+
...implementations.objects,
323+
...implementations.interfaces,
324+
]);
327325
}
328326

329-
this._subTypeMap[abstractType.name] = map;
327+
this._subTypeMap.set(abstractType, set);
330328
}
331-
return map[maybeSubType.name] !== undefined;
329+
return set.has(maybeSubType);
332330
}
333331

334332
getDirectives(): ReadonlyArray<GraphQLDirective> {

src/type/validate.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ function validateInterfaces(
326326
context: SchemaValidationContext,
327327
type: GraphQLObjectType | GraphQLInterfaceType,
328328
): void {
329-
const ifaceTypeNames = Object.create(null);
329+
const ifaceTypeNames = new Set<string>();
330330
for (const iface of type.getInterfaces()) {
331331
if (!isInterfaceType(iface)) {
332332
context.reportError(
@@ -345,15 +345,15 @@ function validateInterfaces(
345345
continue;
346346
}
347347

348-
if (ifaceTypeNames[iface.name]) {
348+
if (ifaceTypeNames.has(iface.name)) {
349349
context.reportError(
350350
`Type ${type.name} can only implement ${iface.name} once.`,
351351
getAllImplementsInterfaceNodes(type, iface),
352352
);
353353
continue;
354354
}
355355

356-
ifaceTypeNames[iface.name] = true;
356+
ifaceTypeNames.add(iface.name);
357357

358358
validateTypeImplementsAncestors(context, type, iface);
359359
validateTypeImplementsInterface(context, type, iface);
@@ -470,16 +470,16 @@ function validateUnionMembers(
470470
);
471471
}
472472

473-
const includedTypeNames = Object.create(null);
473+
const includedTypeNames = new Set<string>();
474474
for (const memberType of memberTypes) {
475-
if (includedTypeNames[memberType.name]) {
475+
if (includedTypeNames.has(memberType.name)) {
476476
context.reportError(
477477
`Union type ${union.name} can only include type ${memberType.name} once.`,
478478
getUnionMemberTypeNodes(union, memberType.name),
479479
);
480480
continue;
481481
}
482-
includedTypeNames[memberType.name] = true;
482+
includedTypeNames.add(memberType.name);
483483
if (!isObjectType(memberType)) {
484484
context.reportError(
485485
`Union type ${union.name} can only include Object types, ` +
@@ -551,7 +551,7 @@ function createInputObjectCircularRefsValidator(
551551
// Modified copy of algorithm from 'src/validation/rules/NoFragmentCycles.js'.
552552
// Tracks already visited types to maintain O(N) and to ensure that cycles
553553
// are not redundantly reported.
554-
const visitedTypes = Object.create(null);
554+
const visitedTypes = new Set<GraphQLInputObjectType>();
555555

556556
// Array of types nodes used to produce meaningful errors
557557
const fieldPath: Array<GraphQLInputField> = [];
@@ -565,11 +565,11 @@ function createInputObjectCircularRefsValidator(
565565
// It does not terminate when a cycle was found but continues to explore
566566
// the graph to find all possible cycles.
567567
function detectCycleRecursive(inputObj: GraphQLInputObjectType): void {
568-
if (visitedTypes[inputObj.name]) {
568+
if (visitedTypes.has(inputObj)) {
569569
return;
570570
}
571571

572-
visitedTypes[inputObj.name] = true;
572+
visitedTypes.add(inputObj);
573573
fieldPathIndexByTypeName[inputObj.name] = fieldPath.length;
574574

575575
const fields = Object.values(inputObj.getFields());

src/validation/ValidationContext.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,14 @@ export class ASTValidationContext {
114114
let fragments = this._recursivelyReferencedFragments.get(operation);
115115
if (!fragments) {
116116
fragments = [];
117-
const collectedNames = Object.create(null);
117+
const collectedNames = new Set<string>();
118118
const nodesToVisit: Array<SelectionSetNode> = [operation.selectionSet];
119119
let node: SelectionSetNode | undefined;
120120
while ((node = nodesToVisit.pop())) {
121121
for (const spread of this.getFragmentSpreads(node)) {
122122
const fragName = spread.name.value;
123-
if (collectedNames[fragName] !== true) {
124-
collectedNames[fragName] = true;
123+
if (!collectedNames.has(fragName)) {
124+
collectedNames.add(fragName);
125125
const fragment = this.getFragment(fragName);
126126
if (fragment) {
127127
fragments.push(fragment);

src/validation/rules/KnownTypeNamesRule.ts

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,34 +30,27 @@ import type {
3030
export function KnownTypeNamesRule(
3131
context: ValidationContext | SDLValidationContext,
3232
): ASTVisitor {
33-
const schema = context.getSchema();
34-
const existingTypesMap = schema ? schema.getTypeMap() : Object.create(null);
33+
const { definitions } = context.getDocument();
34+
const existingTypesMap = context.getSchema()?.getTypeMap() ?? {};
3535

36-
const definedTypes = Object.create(null);
37-
for (const def of context.getDocument().definitions) {
38-
if (isTypeDefinitionNode(def)) {
39-
definedTypes[def.name.value] = true;
40-
}
41-
}
42-
43-
const typeNames = [
36+
const typeNames = new Set([
4437
...Object.keys(existingTypesMap),
45-
...Object.keys(definedTypes),
46-
];
38+
...definitions.filter(isTypeDefinitionNode).map((def) => def.name.value),
39+
]);
4740

4841
return {
4942
NamedType(node, _1, parent, _2, ancestors) {
5043
const typeName = node.name.value;
51-
if (!existingTypesMap[typeName] && !definedTypes[typeName]) {
44+
if (!typeNames.has(typeName)) {
5245
const definitionNode = ancestors[2] ?? parent;
5346
const isSDL = definitionNode != null && isSDLNode(definitionNode);
54-
if (isSDL && standardTypeNames.includes(typeName)) {
47+
if (isSDL && standardTypeNames.has(typeName)) {
5548
return;
5649
}
5750

5851
const suggestedTypes = suggestionList(
5952
typeName,
60-
isSDL ? standardTypeNames.concat(typeNames) : typeNames,
53+
isSDL ? [...standardTypeNames, ...typeNames] : [...typeNames],
6154
);
6255
context.reportError(
6356
new GraphQLError(
@@ -70,8 +63,8 @@ export function KnownTypeNamesRule(
7063
};
7164
}
7265

73-
const standardTypeNames = [...specifiedScalarTypes, ...introspectionTypes].map(
74-
(type) => type.name,
66+
const standardTypeNames = new Set<string>(
67+
[...specifiedScalarTypes, ...introspectionTypes].map((type) => type.name),
7568
);
7669

7770
function isSDLNode(value: ASTNode | ReadonlyArray<ASTNode>): boolean {

src/validation/rules/NoFragmentCyclesRule.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function NoFragmentCyclesRule(
2323
): ASTVisitor {
2424
// Tracks already visited fragments to maintain O(N) and to ensure that cycles
2525
// are not redundantly reported.
26-
const visitedFrags: ObjMap<boolean> = Object.create(null);
26+
const visitedFrags = new Set<string>();
2727

2828
// Array of AST nodes used to produce meaningful errors
2929
const spreadPath: Array<FragmentSpreadNode> = [];
@@ -43,12 +43,12 @@ export function NoFragmentCyclesRule(
4343
// It does not terminate when a cycle was found but continues to explore
4444
// the graph to find all possible cycles.
4545
function detectCycleRecursive(fragment: FragmentDefinitionNode): void {
46-
if (visitedFrags[fragment.name.value]) {
46+
if (visitedFrags.has(fragment.name.value)) {
4747
return;
4848
}
4949

5050
const fragmentName = fragment.name.value;
51-
visitedFrags[fragmentName] = true;
51+
visitedFrags.add(fragmentName);
5252

5353
const spreadNodes = context.getFragmentSpreads(fragment.selectionSet);
5454
if (spreadNodes.length === 0) {

src/validation/rules/NoUndefinedVariablesRule.ts

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,26 @@ import type { ValidationContext } from '../ValidationContext';
1515
export function NoUndefinedVariablesRule(
1616
context: ValidationContext,
1717
): ASTVisitor {
18-
let variableNameDefined = Object.create(null);
19-
2018
return {
21-
OperationDefinition: {
22-
enter() {
23-
variableNameDefined = Object.create(null);
24-
},
25-
leave(operation) {
26-
const usages = context.getRecursiveVariableUsages(operation);
19+
OperationDefinition(operation) {
20+
const variableNameDefined = new Set<string>(
21+
operation.variableDefinitions?.map((node) => node.variable.name.value),
22+
);
2723

28-
for (const { node } of usages) {
29-
const varName = node.name.value;
30-
if (variableNameDefined[varName] !== true) {
31-
context.reportError(
32-
new GraphQLError(
33-
operation.name
34-
? `Variable "$${varName}" is not defined by operation "${operation.name.value}".`
35-
: `Variable "$${varName}" is not defined.`,
36-
{ nodes: [node, operation] },
37-
),
38-
);
39-
}
24+
const usages = context.getRecursiveVariableUsages(operation);
25+
for (const { node } of usages) {
26+
const varName = node.name.value;
27+
if (!variableNameDefined.has(varName)) {
28+
context.reportError(
29+
new GraphQLError(
30+
operation.name
31+
? `Variable "$${varName}" is not defined by operation "${operation.name.value}".`
32+
: `Variable "$${varName}" is not defined.`,
33+
{ nodes: [node, operation] },
34+
),
35+
);
4036
}
41-
},
42-
},
43-
VariableDefinition(node) {
44-
variableNameDefined[node.variable.name.value] = true;
37+
}
4538
},
4639
};
4740
}

src/validation/rules/NoUnusedFragmentsRule.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { GraphQLError } from '../../error/GraphQLError';
22

3-
import type {
4-
FragmentDefinitionNode,
5-
OperationDefinitionNode,
6-
} from '../../language/ast';
3+
import type { FragmentDefinitionNode } from '../../language/ast';
74
import type { ASTVisitor } from '../../language/visitor';
85

96
import type { ASTValidationContext } from '../ValidationContext';
@@ -19,12 +16,16 @@ import type { ASTValidationContext } from '../ValidationContext';
1916
export function NoUnusedFragmentsRule(
2017
context: ASTValidationContext,
2118
): ASTVisitor {
22-
const operationDefs: Array<OperationDefinitionNode> = [];
19+
const fragmentNameUsed = new Set<string>();
2320
const fragmentDefs: Array<FragmentDefinitionNode> = [];
2421

2522
return {
26-
OperationDefinition(node) {
27-
operationDefs.push(node);
23+
OperationDefinition(operation) {
24+
for (const fragment of context.getRecursivelyReferencedFragments(
25+
operation,
26+
)) {
27+
fragmentNameUsed.add(fragment.name.value);
28+
}
2829
return false;
2930
},
3031
FragmentDefinition(node) {
@@ -33,18 +34,9 @@ export function NoUnusedFragmentsRule(
3334
},
3435
Document: {
3536
leave() {
36-
const fragmentNameUsed = Object.create(null);
37-
for (const operation of operationDefs) {
38-
for (const fragment of context.getRecursivelyReferencedFragments(
39-
operation,
40-
)) {
41-
fragmentNameUsed[fragment.name.value] = true;
42-
}
43-
}
44-
4537
for (const fragmentDef of fragmentDefs) {
4638
const fragName = fragmentDef.name.value;
47-
if (fragmentNameUsed[fragName] !== true) {
39+
if (!fragmentNameUsed.has(fragName)) {
4840
context.reportError(
4941
new GraphQLError(`Fragment "${fragName}" is never used.`, {
5042
nodes: fragmentDef,
Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { GraphQLError } from '../../error/GraphQLError';
22

3-
import type { VariableDefinitionNode } from '../../language/ast';
43
import type { ASTVisitor } from '../../language/visitor';
54

65
import type { ValidationContext } from '../ValidationContext';
@@ -14,38 +13,29 @@ import type { ValidationContext } from '../ValidationContext';
1413
* See https://spec.graphql.org/draft/#sec-All-Variables-Used
1514
*/
1615
export function NoUnusedVariablesRule(context: ValidationContext): ASTVisitor {
17-
let variableDefs: Array<VariableDefinitionNode> = [];
18-
1916
return {
20-
OperationDefinition: {
21-
enter() {
22-
variableDefs = [];
23-
},
24-
leave(operation) {
25-
const variableNameUsed = Object.create(null);
26-
const usages = context.getRecursiveVariableUsages(operation);
27-
28-
for (const { node } of usages) {
29-
variableNameUsed[node.name.value] = true;
30-
}
17+
OperationDefinition(operation) {
18+
const usages = context.getRecursiveVariableUsages(operation);
19+
const variableNameUsed = new Set<string>(
20+
usages.map(({ node }) => node.name.value),
21+
);
3122

32-
for (const variableDef of variableDefs) {
33-
const variableName = variableDef.variable.name.value;
34-
if (variableNameUsed[variableName] !== true) {
35-
context.reportError(
36-
new GraphQLError(
37-
operation.name
38-
? `Variable "$${variableName}" is never used in operation "${operation.name.value}".`
39-
: `Variable "$${variableName}" is never used.`,
40-
{ nodes: variableDef },
41-
),
42-
);
43-
}
23+
// FIXME: https://github.com/graphql/graphql-js/issues/2203
24+
/* c8 ignore next */
25+
const variableDefinitions = operation.variableDefinitions ?? [];
26+
for (const variableDef of variableDefinitions) {
27+
const variableName = variableDef.variable.name.value;
28+
if (!variableNameUsed.has(variableName)) {
29+
context.reportError(
30+
new GraphQLError(
31+
operation.name
32+
? `Variable "$${variableName}" is never used in operation "${operation.name.value}".`
33+
: `Variable "$${variableName}" is never used.`,
34+
{ nodes: variableDef },
35+
),
36+
);
4437
}
45-
},
46-
},
47-
VariableDefinition(def) {
48-
variableDefs.push(def);
38+
}
4939
},
5040
};
5141
}

0 commit comments

Comments
 (0)