Skip to content

Commit 1bdd39a

Browse files
committed
Address all unchecked indexed access within source code
1 parent 34eaf2b commit 1bdd39a

17 files changed

+153
-60
lines changed

src/execution/execute.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,12 @@ function executeField(
688688
asyncPayloadRecord?: AsyncPayloadRecord,
689689
): PromiseOrValue<unknown> {
690690
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
691-
const fieldName = fieldNodes[0].name.value;
691+
const firstFieldNode = fieldNodes[0];
692+
invariant(firstFieldNode !== undefined);
693+
694+
const fieldName = fieldNodes[0]?.name.value;
695+
invariant(fieldName !== undefined);
696+
692697
const fieldDef = exeContext.schema.getField(parentType, fieldName);
693698
if (!fieldDef) {
694699
return;
@@ -712,7 +717,7 @@ function executeField(
712717
// TODO: find a way to memoize, in case this field is within a List type.
713718
const args = getArgumentValues(
714719
fieldDef,
715-
fieldNodes[0],
720+
firstFieldNode,
716721
exeContext.variableValues,
717722
);
718723

@@ -974,11 +979,14 @@ function getStreamValues(
974979
return;
975980
}
976981

982+
const firstFieldNode = fieldNodes[0];
983+
invariant(firstFieldNode !== undefined);
984+
977985
// validation only allows equivalent streams on multiple fields, so it is
978986
// safe to only check the first fieldNode for the stream directive
979987
const stream = getDirectiveValues(
980988
GraphQLStreamDirective,
981-
fieldNodes[0],
989+
firstFieldNode,
982990
exeContext.variableValues,
983991
);
984992

@@ -1497,27 +1505,31 @@ export const defaultTypeResolver: GraphQLTypeResolver<unknown, unknown> =
14971505

14981506
// Otherwise, test each possible type.
14991507
const possibleTypes = info.schema.getPossibleTypes(abstractType);
1500-
const promisedIsTypeOfResults = [];
1508+
const promisedIsTypeOfResults: Array<Promise<[string, boolean]>> = [];
15011509

1502-
for (let i = 0; i < possibleTypes.length; i++) {
1503-
const type = possibleTypes[i];
1510+
let type: GraphQLObjectType;
15041511

1512+
for (type of possibleTypes) {
15051513
if (type.isTypeOf) {
15061514
const isTypeOfResult = type.isTypeOf(value, contextValue, info);
15071515

15081516
if (isPromise(isTypeOfResult)) {
1509-
promisedIsTypeOfResults[i] = isTypeOfResult;
1517+
const possibleTypeName = type.name;
1518+
promisedIsTypeOfResults.push(
1519+
isTypeOfResult.then((result) => [possibleTypeName, result]),
1520+
);
15101521
} else if (isTypeOfResult) {
15111522
return type.name;
15121523
}
15131524
}
15141525
}
15151526

15161527
if (promisedIsTypeOfResults.length) {
1528+
// QUESTION: Can this be faster if Promise.any or Promise.race is used instead?
15171529
return Promise.all(promisedIsTypeOfResults).then((isTypeOfResults) => {
1518-
for (let i = 0; i < isTypeOfResults.length; i++) {
1519-
if (isTypeOfResults[i]) {
1520-
return possibleTypes[i].name;
1530+
for (const [name, result] of isTypeOfResults) {
1531+
if (result) {
1532+
return name;
15211533
}
15221534
}
15231535
});

src/jsutils/formatList.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ export function andList(items: ReadonlyArray<string>): string {
1515
}
1616

1717
function formatList(conjunction: string, items: ReadonlyArray<string>): string {
18-
invariant(items.length !== 0);
18+
const firstItem = items[0];
19+
invariant(firstItem !== undefined);
1920

2021
switch (items.length) {
2122
case 1:
22-
return items[0];
23+
return firstItem;
2324
case 2:
24-
return items[0] + ' ' + conjunction + ' ' + items[1];
25+
return firstItem + ' ' + conjunction + ' ' + items[1];
2526
}
2627

2728
const allButLast = items.slice(0, -1);

src/jsutils/mapValue.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ export function mapValue<T, V>(
88
map: ReadOnlyObjMap<T>,
99
fn: (value: T, key: string) => V,
1010
): ObjMap<V> {
11-
const result = Object.create(null);
12-
13-
for (const key of Object.keys(map)) {
14-
result[key] = fn(map[key], key);
15-
}
16-
return result;
11+
return Object.entries(map).reduce((accumulator, [key, value]) => {
12+
accumulator[key] = fn(value, key);
13+
return accumulator;
14+
}, Object.create(null));
1715
}

src/jsutils/promiseForObject.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { invariant } from './invariant.js';
12
import type { ObjMap } from './ObjMap.js';
23

34
/**
@@ -15,8 +16,12 @@ export async function promiseForObject<T>(
1516

1617
const resolvedValues = await Promise.all(values);
1718
const resolvedObject = Object.create(null);
19+
1820
for (let i = 0; i < keys.length; ++i) {
19-
resolvedObject[keys[i]] = resolvedValues[i];
21+
const key = keys[i];
22+
invariant(key !== undefined);
23+
24+
resolvedObject[key] = resolvedValues[i];
2025
}
2126
return resolvedObject;
2227
}

src/jsutils/suggestionList.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { invariant } from './invariant.js';
12
import { naturalCompare } from './naturalCompare.js';
23

34
/**
@@ -93,19 +94,34 @@ class LexicalDistance {
9394
const upRow = rows[(i - 1) % 3];
9495
const currentRow = rows[i % 3];
9596

97+
invariant(upRow !== undefined);
98+
invariant(currentRow !== undefined);
99+
96100
let smallestCell = (currentRow[0] = i);
97101
for (let j = 1; j <= bLength; j++) {
98102
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
99103

104+
const deleteTarget = upRow[j];
105+
const currentRowTarget = currentRow[j - 1];
106+
const substituteTarget = upRow[j - 1];
107+
108+
invariant(deleteTarget !== undefined);
109+
invariant(currentRowTarget !== undefined);
110+
invariant(substituteTarget !== undefined);
111+
100112
let currentCell = Math.min(
101-
upRow[j] + 1, // delete
102-
currentRow[j - 1] + 1, // insert
103-
upRow[j - 1] + cost, // substitute
113+
deleteTarget + 1, // delete
114+
currentRowTarget + 1, // insert
115+
substituteTarget + cost, // substitute
104116
);
105117

106118
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
107119
// transposition
108-
const doubleDiagonalCell = rows[(i - 2) % 3][j - 2];
120+
const targetedRow = rows[(i - 2) % 3];
121+
invariant(targetedRow !== undefined);
122+
const doubleDiagonalCell = targetedRow[j - 2];
123+
invariant(doubleDiagonalCell !== undefined);
124+
109125
currentCell = Math.min(currentCell, doubleDiagonalCell + 1);
110126
}
111127

@@ -122,8 +138,15 @@ class LexicalDistance {
122138
}
123139
}
124140

125-
const distance = rows[aLength % 3][bLength];
126-
return distance <= threshold ? distance : undefined;
141+
const targetedRow = rows[aLength % 3];
142+
143+
invariant(targetedRow !== undefined);
144+
145+
const distance = targetedRow[bLength];
146+
147+
return distance !== undefined && distance <= threshold
148+
? distance
149+
: undefined;
127150
}
128151
}
129152

src/language/blockString.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ export function dedentBlockStringLines(
1515
let firstNonEmptyLine = null;
1616
let lastNonEmptyLine = -1;
1717

18-
for (let i = 0; i < lines.length; ++i) {
19-
const line = lines[i];
18+
lines.forEach((line, i) => {
2019
const indent = leadingWhitespace(line);
2120

2221
if (indent === line.length) {
23-
continue; // skip empty lines
22+
return; // skip empty lines
2423
}
2524

2625
firstNonEmptyLine ??= i;
@@ -29,7 +28,7 @@ export function dedentBlockStringLines(
2928
if (i !== 0 && indent < commonIndent) {
3029
commonIndent = indent;
3130
}
32-
}
31+
});
3332

3433
return (
3534
lines

src/language/printLocation.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { invariant } from '../jsutils/invariant.js';
2+
13
import type { Location } from './ast.js';
24
import type { SourceLocation } from './location.js';
35
import { getLocation } from './location.js';
@@ -35,35 +37,53 @@ export function printSourceLocation(
3537
const locationLine = lines[lineIndex];
3638

3739
// Special case for minified documents
38-
if (locationLine.length > 120) {
40+
if (locationLine !== undefined && locationLine.length > 120) {
3941
const subLineIndex = Math.floor(columnNum / 80);
4042
const subLineColumnNum = columnNum % 80;
4143
const subLines: Array<string> = [];
4244
for (let i = 0; i < locationLine.length; i += 80) {
4345
subLines.push(locationLine.slice(i, i + 80));
4446
}
4547

48+
const firstSubLine = subLines[0];
49+
const nextSubLines = subLines.slice(1, subLineIndex + 1);
50+
const nextSubLine = subLines[subLineIndex + 1];
51+
52+
invariant(firstSubLine !== undefined);
53+
// invariant(nextSubLine !== undefined);
54+
4655
return (
4756
locationStr +
4857
printPrefixedLines([
49-
[`${lineNum} |`, subLines[0]],
50-
...subLines
51-
.slice(1, subLineIndex + 1)
52-
.map((subLine) => ['|', subLine] as const),
58+
[`${lineNum} |`, firstSubLine],
59+
...nextSubLines.map<[string, string]>((subLine) => ['|', subLine]),
5360
['|', '^'.padStart(subLineColumnNum)],
54-
['|', subLines[subLineIndex + 1]],
61+
// TODO: This assertion can be removed if the above invariant is comment in.
62+
['|', nextSubLine as string],
5563
])
5664
);
5765
}
5866

67+
const previousLine = lines[lineIndex - 1];
68+
const nextLine = lines[lineIndex + 1];
69+
70+
// TODO: With the way the types are set up, we should be able to
71+
// comment these in, but doing so breaks tests.
72+
//
73+
// invariant(previousLine !== undefined);
74+
// invariant(nextLine !== undefined);
75+
invariant(locationLine !== undefined);
76+
5977
return (
6078
locationStr +
6179
printPrefixedLines([
6280
// Lines specified like this: ["prefix", "string"],
63-
[`${lineNum - 1} |`, lines[lineIndex - 1]],
81+
// TODO: This assertion can be removed if the above invariant is comment in.
82+
[`${lineNum - 1} |`, previousLine as string],
6483
[`${lineNum} |`, locationLine],
6584
['|', '^'.padStart(columnNum)],
66-
[`${lineNum + 1} |`, lines[lineIndex + 1]],
85+
// TODO: This assertion can be removed if the above invariant is comment in.
86+
[`${lineNum + 1} |`, nextLine as string],
6787
])
6888
);
6989
}

src/language/printString.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { invariant } from '../jsutils/invariant.js';
2+
13
/**
24
* Prints a string as a GraphQL StringValue literal. Replaces control characters
35
* and excluded characters (" U+0022 and \\ U+005C) with escape sequences.
@@ -10,7 +12,15 @@ export function printString(str: string): string {
1012
const escapedRegExp = /[\x00-\x1f\x22\x5c\x7f-\x9f]/g;
1113

1214
function escapedReplacer(str: string): string {
13-
return escapeSequences[str.charCodeAt(0)];
15+
const firstCharacter = str.charCodeAt(0);
16+
17+
invariant(firstCharacter !== undefined);
18+
19+
const replacer = escapeSequences[firstCharacter];
20+
21+
invariant(replacer !== undefined);
22+
23+
return replacer;
1424
}
1525

1626
// prettier-ignore

src/language/visitor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,12 @@ export function visitInParallel(
319319
const enterList = new Array(visitors.length).fill(undefined);
320320
const leaveList = new Array(visitors.length).fill(undefined);
321321

322-
for (let i = 0; i < visitors.length; ++i) {
323-
const { enter, leave } = getEnterLeaveForKind(visitors[i], kind);
322+
visitors.forEach((visitor, i) => {
323+
const { enter, leave } = getEnterLeaveForKind(visitor, kind);
324324
hasVisitor ||= enter != null || leave != null;
325325
enterList[i] = enter;
326326
leaveList[i] = leave;
327-
}
327+
});
328328

329329
if (!hasVisitor) {
330330
continue;

src/utilities/lexicographicSortSchema.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,11 @@ function sortObjMap<T, R>(
166166
sortValueFn: (value: T) => R,
167167
): ObjMap<R> {
168168
const sortedMap = Object.create(null);
169-
for (const key of Object.keys(map).sort(naturalCompare)) {
170-
sortedMap[key] = sortValueFn(map[key]);
169+
170+
for (const [key, value] of Object.entries(map).sort(
171+
([firstKey], [secondKey]) => naturalCompare(firstKey, secondKey),
172+
)) {
173+
sortedMap[key] = sortValueFn(value);
171174
}
172175
return sortedMap;
173176
}

src/validation/rules/DeferStreamDirectiveOnValidOperationsRule.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { invariant } from '../../jsutils/invariant.js';
2+
13
import { GraphQLError } from '../../error/GraphQLError.js';
24

35
import type { DirectiveNode } from '../../language/ast.js';
@@ -50,6 +52,8 @@ export function DeferStreamDirectiveOnValidOperationsRule(
5052
Directive(node, _key, _parent, _path, ancestors) {
5153
const definitionNode = ancestors[2];
5254

55+
invariant(definitionNode !== undefined);
56+
5357
if (
5458
'kind' in definitionNode &&
5559
((definitionNode.kind === Kind.FRAGMENT_DEFINITION &&

src/validation/rules/KnownDirectivesRule.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ function getDirectiveLocationForASTPath(
7373
ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>,
7474
): DirectiveLocation | undefined {
7575
const appliedTo = ancestors[ancestors.length - 1];
76+
invariant(appliedTo !== undefined);
7677
invariant('kind' in appliedTo);
7778

7879
switch (appliedTo.kind) {
@@ -115,6 +116,7 @@ function getDirectiveLocationForASTPath(
115116
return DirectiveLocation.INPUT_OBJECT;
116117
case Kind.INPUT_VALUE_DEFINITION: {
117118
const parentNode = ancestors[ancestors.length - 3];
119+
invariant(parentNode !== undefined);
118120
invariant('kind' in parentNode);
119121
return parentNode.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION
120122
? DirectiveLocation.INPUT_FIELD_DEFINITION

0 commit comments

Comments
 (0)