Skip to content

New: printError() #1129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/error/GraphQLError.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
* @flow
*/

import { printError } from './printError';
import { getLocation } from '../language/location';
import type { SourceLocation } from '../language/location';
import type { ASTNode } from '../language/ast';
import type { Source } from '../language/source';

export type GraphQLErrorLocation = {|
+line: number,
+column: number,
|};

/**
* A GraphQLError describes an Error found during the parse, validate, or
* execute phases of performing a GraphQL operation. In addition to a message
Expand Down Expand Up @@ -52,7 +49,7 @@ declare class GraphQLError extends Error {
*
* Enumerable, and appears in the result of JSON.stringify().
*/
+locations: $ReadOnlyArray<GraphQLErrorLocation> | void;
+locations: $ReadOnlyArray<SourceLocation> | void;

/**
* An array describing the JSON-path into the execution response which
Expand Down Expand Up @@ -194,4 +191,9 @@ export function GraphQLError( // eslint-disable-line no-redeclare
(GraphQLError: any).prototype = Object.create(Error.prototype, {
constructor: { value: GraphQLError },
name: { value: 'GraphQLError' },
toString: {
value: function toString() {
return printError(this);
},
},
});
5 changes: 3 additions & 2 deletions src/error/formatError.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
*/

import invariant from '../jsutils/invariant';
import type { GraphQLError, GraphQLErrorLocation } from './GraphQLError';
import type { GraphQLError } from './GraphQLError';
import type { SourceLocation } from '../language/location';

/**
* Given a GraphQLError, format it according to the rules described by the
Expand All @@ -26,7 +27,7 @@ export function formatError(error: GraphQLError): GraphQLFormattedError {

export type GraphQLFormattedError = {
+message: string,
+locations: $ReadOnlyArray<GraphQLErrorLocation> | void,
+locations: $ReadOnlyArray<SourceLocation> | void,
+path: $ReadOnlyArray<string | number> | void,
// Extensions
+[key: string]: mixed,
Expand Down
2 changes: 1 addition & 1 deletion src/error/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
export { GraphQLError } from './GraphQLError';
export { syntaxError } from './syntaxError';
export { locatedError } from './locatedError';
export { printError } from './printError';
export { formatError } from './formatError';

export type { GraphQLErrorLocation } from './GraphQLError';
export type { GraphQLFormattedError } from './formatError';
76 changes: 76 additions & 0 deletions src/error/printError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type { SourceLocation } from '../language/location';
import type { Source } from '../language/source';
import type { GraphQLError } from './GraphQLError';

/**
* Prints a GraphQLError to a string, representing useful location information
* about the error's position in the source.
*/
export function printError(error: GraphQLError): string {
const source = error.source;
const locations = error.locations || [];
const printedLocations = locations.map(
location =>
source
? highlightSourceAtLocation(source, location)
: ` (${location.line}:${location.column})`,
);
return error.message + printedLocations.join('');
}

/**
* Render a helpful description of the location of the error in the GraphQL
* Source document.
*/
function highlightSourceAtLocation(
source: Source,
location: SourceLocation,
): string {
const line = location.line;
const lineOffset = source.locationOffset.line - 1;
const columnOffset = getColumnOffset(source, location);
const contextLine = line + lineOffset;
const contextColumn = location.column + columnOffset;
const prevLineNum = (contextLine - 1).toString();
const lineNum = contextLine.toString();
const nextLineNum = (contextLine + 1).toString();
const padLen = nextLineNum.length;
const lines = source.body.split(/\r\n|[\n\r]/g);
lines[0] = whitespace(source.locationOffset.column - 1) + lines[0];
return (
`\n\n${source.name} (${contextLine}:${contextColumn})\n` +
(line >= 2
? lpad(padLen, prevLineNum) + ': ' + lines[line - 2] + '\n'
: '') +
lpad(padLen, lineNum) +
': ' +
lines[line - 1] +
'\n' +
whitespace(2 + padLen + contextColumn - 1) +
'^\n' +
(line < lines.length
? lpad(padLen, nextLineNum) + ': ' + lines[line] + '\n'
: '')
);
}

function getColumnOffset(source: Source, location: SourceLocation): number {
return location.line === 1 ? source.locationOffset.column - 1 : 0;
}

function whitespace(len: number): string {
return Array(len + 1).join(' ');
}

function lpad(len: number, str: string): string {
return whitespace(len - str.length) + str;
}
62 changes: 3 additions & 59 deletions src/error/syntaxError.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
* @flow
*/

import { getLocation } from '../language/location';
import type { Source } from '../language/source';
import { GraphQLError } from './GraphQLError';

import type { SourceLocation } from '../language/location';

/**
* Produces a GraphQLError representing a syntax error, containing useful
* descriptive information about the syntax error's position in the source.
Expand All @@ -22,60 +19,7 @@ export function syntaxError(
position: number,
description: string,
): GraphQLError {
const location = getLocation(source, position);
const line = location.line + source.locationOffset.line - 1;
const columnOffset = getColumnOffset(source, location);
const column = location.column + columnOffset;
const error = new GraphQLError(
`Syntax Error ${source.name} (${line}:${column}) ${description}` +
'\n\n' +
highlightSourceAtLocation(source, location),
undefined,
source,
[position],
);
return error;
}

/**
* Render a helpful description of the location of the error in the GraphQL
* Source document.
*/
function highlightSourceAtLocation(source, location) {
const line = location.line;
const lineOffset = source.locationOffset.line - 1;
const columnOffset = getColumnOffset(source, location);
const contextLine = line + lineOffset;
const prevLineNum = (contextLine - 1).toString();
const lineNum = contextLine.toString();
const nextLineNum = (contextLine + 1).toString();
const padLen = nextLineNum.length;
const lines = source.body.split(/\r\n|[\n\r]/g);
lines[0] = whitespace(source.locationOffset.column - 1) + lines[0];
return (
(line >= 2
? lpad(padLen, prevLineNum) + ': ' + lines[line - 2] + '\n'
: '') +
lpad(padLen, lineNum) +
': ' +
lines[line - 1] +
'\n' +
whitespace(2 + padLen + location.column - 1 + columnOffset) +
'^\n' +
(line < lines.length
? lpad(padLen, nextLineNum) + ': ' + lines[line] + '\n'
: '')
);
}

function getColumnOffset(source: Source, location: SourceLocation): number {
return location.line === 1 ? source.locationOffset.column - 1 : 0;
}

function whitespace(len) {
return Array(len + 1).join(' ');
}

function lpad(len, str) {
return whitespace(len - str.length) + str;
return new GraphQLError(`Syntax Error: ${description}`, undefined, source, [
position,
]);
}
5 changes: 1 addition & 4 deletions src/execution/__tests__/sync-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,7 @@ describe('Execute: synchronously when possible', () => {
expect(result).to.containSubset({
errors: [
{
message:
'Syntax Error GraphQL request (1:29) Expected Name, found {\n\n' +
'1: fragment Example on Query { { { syncField }\n' +
' ^\n',
message: 'Syntax Error: Expected Name, found {',
locations: [{ line: 1, column: 29 }],
},
],
Expand Down
7 changes: 4 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export type {
// Parse and operate on GraphQL language source files.
export {
Source,
SourceLocation,
getLocation,
// Parse
parse,
Expand Down Expand Up @@ -265,10 +266,10 @@ export {
VariablesInAllowedPositionRule,
} from './validation';

// Create and format GraphQL errors.
export { GraphQLError, formatError } from './error';
// Create, format, and print GraphQL errors.
export { GraphQLError, formatError, printError } from './error';

export type { GraphQLFormattedError, GraphQLErrorLocation } from './error';
export type { GraphQLFormattedError } from './error';

// Utilities for operating on GraphQL type schema and parsed sources.
export {
Expand Down
Loading