Skip to content

Commit 6b95561

Browse files
committed
TS: Fix strict issues in src/validation
* Add `"strict": true` to tsconfig.json * Fix all issues that arise within `src/validation`
1 parent c589c3d commit 6b95561

13 files changed

+93
-73
lines changed

src/type/definition.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,9 @@ export function getNullableType(type: undefined | null): void;
457457
export function getNullableType<T extends GraphQLNullableType>(
458458
type: T | GraphQLNonNull<T>,
459459
): T;
460+
export function getNullableType(
461+
type: Maybe<GraphQLType>,
462+
): GraphQLNullableType | undefined;
460463
export function getNullableType(
461464
type: Maybe<GraphQLType>,
462465
): GraphQLNullableType | undefined {
@@ -504,6 +507,9 @@ export function getNamedType(type: undefined | null): void;
504507
export function getNamedType(type: GraphQLInputType): GraphQLNamedInputType;
505508
export function getNamedType(type: GraphQLOutputType): GraphQLNamedOutputType;
506509
export function getNamedType(type: GraphQLType): GraphQLNamedType;
510+
export function getNamedType(
511+
type: Maybe<GraphQLType>,
512+
): GraphQLNamedType | undefined;
507513
export function getNamedType(
508514
type: Maybe<GraphQLType>,
509515
): GraphQLNamedType | undefined {

src/validation/ValidationContext.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,11 @@ interface VariableUsage {
4444
export class ASTValidationContext {
4545
private _ast: DocumentNode;
4646
private _onError: (error: GraphQLError) => void;
47-
private _fragments: Maybe<ObjMap<FragmentDefinitionNode>>;
48-
private _fragmentSpreads: Map<
49-
SelectionSetNode,
50-
ReadonlyArray<FragmentSpreadNode>
51-
>;
52-
47+
private _fragments: ObjMap<FragmentDefinitionNode> | undefined;
48+
private _fragmentSpreads: Map<SelectionSetNode, Array<FragmentSpreadNode>>;
5349
private _recursivelyReferencedFragments: Map<
5450
OperationDefinitionNode,
55-
ReadonlyArray<FragmentDefinitionNode>
51+
Array<FragmentDefinitionNode>
5652
>;
5753

5854
constructor(ast: DocumentNode, onError: (error: GraphQLError) => void) {
@@ -72,15 +68,19 @@ export class ASTValidationContext {
7268
}
7369

7470
getFragment(name: string): Maybe<FragmentDefinitionNode> {
75-
if (!this._fragments) {
76-
const fragments = (this._fragments = Object.create(null));
71+
let fragments: ObjMap<FragmentDefinitionNode>;
72+
if (this._fragments) {
73+
fragments = this._fragments;
74+
} else {
75+
fragments = Object.create(null);
7776
for (const defNode of this.getDocument().definitions) {
7877
if (defNode.kind === Kind.FRAGMENT_DEFINITION) {
7978
fragments[defNode.name.value] = defNode;
8079
}
8180
}
81+
this._fragments = fragments;
8282
}
83-
return this._fragments[name];
83+
return fragments[name];
8484
}
8585

8686
getFragmentSpreads(
@@ -90,11 +90,10 @@ export class ASTValidationContext {
9090
if (!spreads) {
9191
spreads = [];
9292
const setsToVisit: Array<SelectionSetNode> = [node];
93-
while (setsToVisit.length !== 0) {
94-
const set = setsToVisit.pop();
93+
let set: SelectionSetNode | undefined;
94+
while ((set = setsToVisit.pop())) {
9595
for (const selection of set.selections) {
9696
if (selection.kind === Kind.FRAGMENT_SPREAD) {
97-
// @ts-expect-error FIXME: TS Conversion
9897
spreads.push(selection);
9998
} else if (selection.selectionSet) {
10099
setsToVisit.push(selection.selectionSet);
@@ -114,15 +113,14 @@ export class ASTValidationContext {
114113
fragments = [];
115114
const collectedNames = Object.create(null);
116115
const nodesToVisit: Array<SelectionSetNode> = [operation.selectionSet];
117-
while (nodesToVisit.length !== 0) {
118-
const node = nodesToVisit.pop();
116+
let node: SelectionSetNode | undefined;
117+
while ((node = nodesToVisit.pop())) {
119118
for (const spread of this.getFragmentSpreads(node)) {
120119
const fragName = spread.name.value;
121120
if (collectedNames[fragName] !== true) {
122121
collectedNames[fragName] = true;
123122
const fragment = this.getFragment(fragName);
124123
if (fragment) {
125-
// @ts-expect-error FIXME: TS Conversion
126124
fragments.push(fragment);
127125
nodesToVisit.push(fragment.selectionSet);
128126
}
@@ -189,7 +187,7 @@ export class ValidationContext extends ASTValidationContext {
189187
getVariableUsages(node: NodeWithSelectionSet): ReadonlyArray<VariableUsage> {
190188
let usages = this._variableUsages.get(node);
191189
if (!usages) {
192-
const newUsages = [];
190+
const newUsages: Array<VariableUsage> = [];
193191
const typeInfo = new TypeInfo(this._schema);
194192
visit(
195193
node,

src/validation/__tests__/validation-test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { describe, it } from 'mocha';
33

44
import { GraphQLError } from '../../error/GraphQLError';
55

6+
import type { DirectiveNode } from '../../language/ast';
67
import { parse } from '../../language/parser';
78

89
import { TypeInfo } from '../../utilities/TypeInfo';
@@ -15,6 +16,7 @@ import { testSchema } from './harness';
1516

1617
describe('Validate: Supports full validation', () => {
1718
it('rejects invalid documents', () => {
19+
// TODO ts-expect-error (expects a DocumentNode as a second parameter)
1820
expect(() => validate(testSchema, null)).to.throw('Must provide document.');
1921
});
2022

@@ -96,7 +98,7 @@ describe('Validate: Supports full validation', () => {
9698

9799
function customRule(context: ValidationContext) {
98100
return {
99-
Directive(node) {
101+
Directive(node: DirectiveNode) {
100102
const directiveDef = context.getDirective();
101103
const error = new GraphQLError(
102104
'Reporting directive: ' + String(directiveDef),

src/validation/rules/KnownDirectivesRule.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,10 @@ function getDirectiveLocationForASTPath(
7171
ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>,
7272
): DirectiveLocationEnum | undefined {
7373
const appliedTo = ancestors[ancestors.length - 1];
74-
invariant(!Array.isArray(appliedTo));
74+
invariant('kind' in appliedTo);
7575

76-
// @ts-expect-error FIXME: TS Conversion
7776
switch (appliedTo.kind) {
7877
case Kind.OPERATION_DEFINITION:
79-
// @ts-expect-error FIXME: TS Conversion
8078
return getDirectiveLocationForOperation(appliedTo.operation);
8179
case Kind.FIELD:
8280
return DirectiveLocation.FIELD;
@@ -115,7 +113,7 @@ function getDirectiveLocationForASTPath(
115113
return DirectiveLocation.INPUT_OBJECT;
116114
case Kind.INPUT_VALUE_DEFINITION: {
117115
const parentNode = ancestors[ancestors.length - 3];
118-
// @ts-expect-error FIXME: TS Conversion
116+
invariant('kind' in parentNode);
119117
return parentNode.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION
120118
? DirectiveLocation.INPUT_FIELD_DEFINITION
121119
: DirectiveLocation.ARGUMENT_DEFINITION;

src/validation/rules/KnownTypeNamesRule.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ const standardTypeNames = [...specifiedScalarTypes, ...introspectionTypes].map(
7474

7575
function isSDLNode(value: ASTNode | ReadonlyArray<ASTNode>): boolean {
7676
return (
77-
!Array.isArray(value) &&
78-
// @ts-expect-error FIXME: TS Conversion
77+
'kind' in value &&
7978
(isTypeSystemDefinitionNode(value) || isTypeSystemExtensionNode(value))
8079
);
8180
}

src/validation/rules/NoFragmentCyclesRule.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import type { ObjMap } from '../../jsutils/ObjMap';
2+
13
import { GraphQLError } from '../../error/GraphQLError';
24

5+
import type {
6+
FragmentDefinitionNode,
7+
FragmentSpreadNode,
8+
} from '../../language/ast';
39
import type { ASTVisitor } from '../../language/visitor';
4-
import type { FragmentDefinitionNode } from '../../language/ast';
510

611
import type { ASTValidationContext } from '../ValidationContext';
712

@@ -10,13 +15,13 @@ export function NoFragmentCyclesRule(
1015
): ASTVisitor {
1116
// Tracks already visited fragments to maintain O(N) and to ensure that cycles
1217
// are not redundantly reported.
13-
const visitedFrags = Object.create(null);
18+
const visitedFrags: ObjMap<boolean> = Object.create(null);
1419

1520
// Array of AST nodes used to produce meaningful errors
16-
const spreadPath = [];
21+
const spreadPath: Array<FragmentSpreadNode> = [];
1722

1823
// Position in the spread path
19-
const spreadPathIndexByName = Object.create(null);
24+
const spreadPathIndexByName: ObjMap<number | undefined> = Object.create(null);
2025

2126
return {
2227
OperationDefinition: () => false,

src/validation/rules/NoUnusedFragmentsRule.ts

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

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

59
import type { ASTValidationContext } from '../ValidationContext';
@@ -13,8 +17,8 @@ import type { ASTValidationContext } from '../ValidationContext';
1317
export function NoUnusedFragmentsRule(
1418
context: ASTValidationContext,
1519
): ASTVisitor {
16-
const operationDefs = [];
17-
const fragmentDefs = [];
20+
const operationDefs: Array<OperationDefinitionNode> = [];
21+
const fragmentDefs: Array<FragmentDefinitionNode> = [];
1822

1923
return {
2024
OperationDefinition(node) {

src/validation/rules/NoUnusedVariablesRule.ts

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

3+
import type { VariableDefinitionNode } from '../../language/ast';
34
import type { ASTVisitor } from '../../language/visitor';
45

56
import type { ValidationContext } from '../ValidationContext';
@@ -11,7 +12,7 @@ import type { ValidationContext } from '../ValidationContext';
1112
* are used, either directly or within a spread fragment.
1213
*/
1314
export function NoUnusedVariablesRule(context: ValidationContext): ASTVisitor {
14-
let variableDefs = [];
15+
let variableDefs: Array<VariableDefinitionNode> = [];
1516

1617
return {
1718
OperationDefinition: {

src/validation/rules/OverlappingFieldsCanBeMergedRule.ts

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import { print } from '../../language/printer';
1717
import type {
1818
GraphQLNamedType,
1919
GraphQLOutputType,
20-
GraphQLCompositeType,
2120
GraphQLField,
2221
} from '../../type/definition';
2322
import {
@@ -97,12 +96,14 @@ type ConflictReason = [string, ConflictReasonMessage];
9796
type ConflictReasonMessage = string | Array<ConflictReason>;
9897
// Tuple defining a field node in a context.
9998
type NodeAndDef = [
100-
GraphQLCompositeType,
99+
Maybe<GraphQLNamedType>,
101100
FieldNode,
102101
Maybe<GraphQLField<unknown, unknown>>,
103102
];
104103
// Map of array of those.
105104
type NodeAndDefCollection = ObjMap<Array<NodeAndDef>>;
105+
type FragmentNames = Array<string>;
106+
type FieldsAndFragmentNames = readonly [NodeAndDefCollection, FragmentNames];
106107

107108
/**
108109
* Algorithm:
@@ -164,12 +165,12 @@ type NodeAndDefCollection = ObjMap<Array<NodeAndDef>>;
164165
// GraphQL Document.
165166
function findConflictsWithinSelectionSet(
166167
context: ValidationContext,
167-
cachedFieldsAndFragmentNames,
168+
cachedFieldsAndFragmentNames: Map<SelectionSetNode, FieldsAndFragmentNames>,
168169
comparedFragmentPairs: PairSet,
169170
parentType: Maybe<GraphQLNamedType>,
170171
selectionSet: SelectionSetNode,
171172
): Array<Conflict> {
172-
const conflicts = [];
173+
const conflicts: Array<Conflict> = [];
173174

174175
const [fieldMap, fragmentNames] = getFieldsAndFragmentNames(
175176
context,
@@ -226,7 +227,7 @@ function findConflictsWithinSelectionSet(
226227
function collectConflictsBetweenFieldsAndFragment(
227228
context: ValidationContext,
228229
conflicts: Array<Conflict>,
229-
cachedFieldsAndFragmentNames,
230+
cachedFieldsAndFragmentNames: Map<SelectionSetNode, FieldsAndFragmentNames>,
230231
comparedFragmentPairs: PairSet,
231232
areMutuallyExclusive: boolean,
232233
fieldMap: NodeAndDefCollection,
@@ -280,7 +281,7 @@ function collectConflictsBetweenFieldsAndFragment(
280281
function collectConflictsBetweenFragments(
281282
context: ValidationContext,
282283
conflicts: Array<Conflict>,
283-
cachedFieldsAndFragmentNames,
284+
cachedFieldsAndFragmentNames: Map<SelectionSetNode, FieldsAndFragmentNames>,
284285
comparedFragmentPairs: PairSet,
285286
areMutuallyExclusive: boolean,
286287
fragmentName1: string,
@@ -366,15 +367,15 @@ function collectConflictsBetweenFragments(
366367
// between the sub-fields of two overlapping fields.
367368
function findConflictsBetweenSubSelectionSets(
368369
context: ValidationContext,
369-
cachedFieldsAndFragmentNames,
370+
cachedFieldsAndFragmentNames: Map<SelectionSetNode, FieldsAndFragmentNames>,
370371
comparedFragmentPairs: PairSet,
371372
areMutuallyExclusive: boolean,
372373
parentType1: Maybe<GraphQLNamedType>,
373374
selectionSet1: SelectionSetNode,
374375
parentType2: Maybe<GraphQLNamedType>,
375376
selectionSet2: SelectionSetNode,
376377
): Array<Conflict> {
377-
const conflicts = [];
378+
const conflicts: Array<Conflict> = [];
378379

379380
const [fieldMap1, fragmentNames1] = getFieldsAndFragmentNames(
380381
context,
@@ -455,7 +456,7 @@ function findConflictsBetweenSubSelectionSets(
455456
function collectConflictsWithin(
456457
context: ValidationContext,
457458
conflicts: Array<Conflict>,
458-
cachedFieldsAndFragmentNames,
459+
cachedFieldsAndFragmentNames: Map<SelectionSetNode, FieldsAndFragmentNames>,
459460
comparedFragmentPairs: PairSet,
460461
fieldMap: NodeAndDefCollection,
461462
): void {
@@ -496,7 +497,7 @@ function collectConflictsWithin(
496497
function collectConflictsBetween(
497498
context: ValidationContext,
498499
conflicts: Array<Conflict>,
499-
cachedFieldsAndFragmentNames,
500+
cachedFieldsAndFragmentNames: Map<SelectionSetNode, FieldsAndFragmentNames>,
500501
comparedFragmentPairs: PairSet,
501502
parentFieldsAreMutuallyExclusive: boolean,
502503
fieldMap1: NodeAndDefCollection,
@@ -534,7 +535,7 @@ function collectConflictsBetween(
534535
// comparing their sub-fields.
535536
function findConflict(
536537
context: ValidationContext,
537-
cachedFieldsAndFragmentNames,
538+
cachedFieldsAndFragmentNames: Map<SelectionSetNode, FieldsAndFragmentNames>,
538539
comparedFragmentPairs: PairSet,
539540
parentFieldsAreMutuallyExclusive: boolean,
540541
responseName: string,
@@ -677,32 +678,33 @@ function doTypesConflict(
677678
// referenced via fragment spreads.
678679
function getFieldsAndFragmentNames(
679680
context: ValidationContext,
680-
cachedFieldsAndFragmentNames,
681+
cachedFieldsAndFragmentNames: Map<SelectionSetNode, FieldsAndFragmentNames>,
681682
parentType: Maybe<GraphQLNamedType>,
682683
selectionSet: SelectionSetNode,
683-
): [NodeAndDefCollection, Array<string>] {
684-
let cached = cachedFieldsAndFragmentNames.get(selectionSet);
685-
if (!cached) {
686-
const nodeAndDefs = Object.create(null);
687-
const fragmentNames = Object.create(null);
688-
_collectFieldsAndFragmentNames(
689-
context,
690-
parentType,
691-
selectionSet,
692-
nodeAndDefs,
693-
fragmentNames,
694-
);
695-
cached = [nodeAndDefs, Object.keys(fragmentNames)];
696-
cachedFieldsAndFragmentNames.set(selectionSet, cached);
684+
): FieldsAndFragmentNames {
685+
const cached = cachedFieldsAndFragmentNames.get(selectionSet);
686+
if (cached) {
687+
return cached;
697688
}
698-
return cached;
689+
const nodeAndDefs: NodeAndDefCollection = Object.create(null);
690+
const fragmentNames: ObjMap<boolean> = Object.create(null);
691+
_collectFieldsAndFragmentNames(
692+
context,
693+
parentType,
694+
selectionSet,
695+
nodeAndDefs,
696+
fragmentNames,
697+
);
698+
const result = [nodeAndDefs, Object.keys(fragmentNames)] as const;
699+
cachedFieldsAndFragmentNames.set(selectionSet, result);
700+
return result;
699701
}
700702

701703
// Given a reference to a fragment, return the represented collection of fields
702704
// as well as a list of nested fragment names referenced via fragment spreads.
703705
function getReferencedFieldsAndFragmentNames(
704706
context: ValidationContext,
705-
cachedFieldsAndFragmentNames,
707+
cachedFieldsAndFragmentNames: Map<SelectionSetNode, FieldsAndFragmentNames>,
706708
fragment: FragmentDefinitionNode,
707709
) {
708710
// Short-circuit building a type from the node if possible.
@@ -724,8 +726,8 @@ function _collectFieldsAndFragmentNames(
724726
context: ValidationContext,
725727
parentType: Maybe<GraphQLNamedType>,
726728
selectionSet: SelectionSetNode,
727-
nodeAndDefs,
728-
fragmentNames,
729+
nodeAndDefs: NodeAndDefCollection,
730+
fragmentNames: ObjMap<boolean>,
729731
): void {
730732
for (const selection of selectionSet.selections) {
731733
switch (selection.kind) {

0 commit comments

Comments
 (0)