Skip to content

Commit b936411

Browse files
GraphQLError: fix empty locations if error got nodes without locations (graphql#3325)
1 parent a3d215b commit b936411

File tree

3 files changed

+39
-44
lines changed

3 files changed

+39
-44
lines changed

src/error/GraphQLError.ts

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { isObjectLike } from '../jsutils/isObjectLike';
22
import type { Maybe } from '../jsutils/Maybe';
33

4-
import type { ASTNode } from '../language/ast';
4+
import type { ASTNode, Location } from '../language/ast';
55
import type { Source } from '../language/source';
66
import type { SourceLocation } from '../language/location';
77
import { getLocation } from '../language/location';
@@ -92,65 +92,40 @@ export class GraphQLError extends Error {
9292
this.originalError = originalError ?? undefined;
9393

9494
// Compute list of blame nodes.
95-
this.nodes = Array.isArray(nodes)
96-
? nodes.length !== 0
97-
? nodes
98-
: undefined
99-
: nodes
100-
? [nodes]
101-
: undefined;
95+
this.nodes = undefinedIfEmpty(
96+
Array.isArray(nodes) ? nodes : nodes ? [nodes] : undefined,
97+
);
98+
99+
const nodeLocations = undefinedIfEmpty(
100+
this.nodes
101+
?.map((node) => node.loc)
102+
.filter((loc): loc is Location => loc != null),
103+
);
102104

103105
// Compute locations in the source for the given nodes/positions.
104-
this.source = source ?? undefined;
105-
if (!this.source && this.nodes) {
106-
this.source = this.nodes[0].loc?.source;
107-
}
106+
this.source = source ?? nodeLocations?.[0]?.source;
108107

109-
if (positions) {
110-
this.positions = positions;
111-
} else if (this.nodes) {
112-
const positionsFromNodes = [];
113-
for (const node of this.nodes) {
114-
if (node.loc) {
115-
positionsFromNodes.push(node.loc.start);
116-
}
117-
}
118-
this.positions = positionsFromNodes;
119-
}
120-
if (this.positions && this.positions.length === 0) {
121-
this.positions = undefined;
122-
}
108+
this.positions = positions ?? nodeLocations?.map((loc) => loc.start);
123109

124-
if (positions && source) {
125-
this.locations = positions.map((pos) => getLocation(source, pos));
126-
} else if (this.nodes) {
127-
const locationsFromNodes = [];
128-
for (const node of this.nodes) {
129-
if (node.loc) {
130-
locationsFromNodes.push(getLocation(node.loc.source, node.loc.start));
131-
}
132-
}
133-
this.locations = locationsFromNodes;
134-
}
110+
this.locations =
111+
positions && source
112+
? positions.map((pos) => getLocation(source, pos))
113+
: nodeLocations?.map((loc) => getLocation(loc.source, loc.start));
135114

136115
const originalExtensions = isObjectLike(originalError?.extensions)
137116
? originalError?.extensions
138117
: undefined;
139-
// TODO: merge `extensions` and `originalExtensions`
140118
this.extensions = extensions ?? originalExtensions ?? Object.create(null);
141119

142120
// Include (non-enumerable) stack trace.
121+
// istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2317')
143122
if (originalError?.stack) {
144123
Object.defineProperty(this, 'stack', {
145124
value: originalError.stack,
146125
writable: true,
147126
configurable: true,
148127
});
149-
return;
150-
}
151-
152-
// istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2317')
153-
if (Error.captureStackTrace) {
128+
} else if (Error.captureStackTrace) {
154129
Error.captureStackTrace(this, GraphQLError);
155130
} else {
156131
Object.defineProperty(this, 'stack', {
@@ -208,6 +183,12 @@ export class GraphQLError extends Error {
208183
}
209184
}
210185

186+
function undefinedIfEmpty<T>(
187+
array: Array<T> | undefined,
188+
): Array<T> | undefined {
189+
return array === undefined || array.length === 0 ? undefined : array;
190+
}
191+
211192
/**
212193
* See: https://spec.graphql.org/draft/#sec-Errors
213194
*/

src/error/__tests__/GraphQLError-test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,21 @@ describe('GraphQLError', () => {
100100
});
101101
});
102102

103+
it('converts node without location to undefined source, positions and locations', () => {
104+
const fieldNodeNoLocation = {
105+
...fieldNode,
106+
loc: undefined,
107+
};
108+
109+
const e = new GraphQLError('msg', fieldNodeNoLocation);
110+
expect(e).to.deep.include({
111+
nodes: [fieldNodeNoLocation],
112+
source: undefined,
113+
positions: undefined,
114+
locations: undefined,
115+
});
116+
});
117+
103118
it('converts source and positions to locations', () => {
104119
const e = new GraphQLError('msg', null, source, [6]);
105120
expect(e).to.deep.include({

src/validation/__tests__/validation-test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ describe('Validate: Limit maximum number of validation errors', () => {
142142
function invalidFieldError(fieldName: string) {
143143
return {
144144
message: `Cannot query field "${fieldName}" on type "QueryRoot".`,
145-
locations: [],
146145
};
147146
}
148147

0 commit comments

Comments
 (0)