Skip to content

Commit

Permalink
Extract 'printLocation' & 'printSourceLocation' functions (#1984)
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanGoncharov committed Jun 14, 2019
1 parent 3ddf148 commit a9a21f3
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 148 deletions.
24 changes: 23 additions & 1 deletion src/error/GraphQLError.js
@@ -1,10 +1,10 @@
// @flow strict

import isObjectLike from '../jsutils/isObjectLike';
import { printError } from './printError';
import { type ASTNode } from '../language/ast';
import { type Source } from '../language/source';
import { type SourceLocation, getLocation } from '../language/location';
import { printLocation, printSourceLocation } from '../language/printLocation';

/**
* A GraphQLError describes an Error found during the parse, validate, or
Expand Down Expand Up @@ -217,3 +217,25 @@ export function GraphQLError( // eslint-disable-line no-redeclare
},
},
});

/**
* Prints a GraphQLError to a string, representing useful location information
* about the error's position in the source.
*/
export function printError(error: GraphQLError): string {
let output = error.message;

if (error.nodes) {
for (const node of error.nodes) {
if (node.loc) {
output += '\n\n' + printLocation(node.loc);
}
}
} else if (error.source && error.locations) {
for (const location of error.locations) {
output += '\n\n' + printSourceLocation(error.source, location);
}
}

return output;
}
77 changes: 76 additions & 1 deletion src/error/__tests__/GraphQLError-test.js
Expand Up @@ -5,7 +5,14 @@ import { describe, it } from 'mocha';

import dedent from '../../jsutils/dedent';
import invariant from '../../jsutils/invariant';
import { Kind, parse, Source, GraphQLError, formatError } from '../../';
import {
Kind,
parse,
Source,
GraphQLError,
printError,
formatError,
} from '../../';

const source = new Source(dedent`
{
Expand Down Expand Up @@ -155,3 +162,71 @@ describe('GraphQLError', () => {
});
});
});

describe('printError', () => {
it('prints an error without location', () => {
const error = new GraphQLError('Error without location');
expect(printError(error)).to.equal('Error without location');
});

it('prints an error using node without location', () => {
const error = new GraphQLError(
'Error attached to node without location',
parse('{ foo }', { noLocation: true }),
);
expect(printError(error)).to.equal(
'Error attached to node without location',
);
});

it('prints an error with nodes from different sources', () => {
const docA = parse(
new Source(
dedent`
type Foo {
field: String
}
`,
'SourceA',
),
);
const opA = docA.definitions[0];
invariant(opA && opA.kind === Kind.OBJECT_TYPE_DEFINITION && opA.fields);
const fieldA = opA.fields[0];

const docB = parse(
new Source(
dedent`
type Foo {
field: Int
}
`,
'SourceB',
),
);
const opB = docB.definitions[0];
invariant(opB && opB.kind === Kind.OBJECT_TYPE_DEFINITION && opB.fields);
const fieldB = opB.fields[0];

const error = new GraphQLError('Example error with two nodes', [
fieldA.type,
fieldB.type,
]);

expect(printError(error) + '\n').to.equal(dedent`
Example error with two nodes
SourceA:2:10
1: type Foo {
2: field: String
^
3: }
SourceB:2:10
1: type Foo {
2: field: Int
^
3: }
`);
});
});
109 changes: 0 additions & 109 deletions src/error/__tests__/printError-test.js

This file was deleted.

4 changes: 1 addition & 3 deletions src/error/index.js
@@ -1,12 +1,10 @@
// @flow strict

export { GraphQLError } from './GraphQLError';
export { GraphQLError, printError } from './GraphQLError';

export { syntaxError } from './syntaxError';

export { locatedError } from './locatedError';

export { printError } from './printError';

export { formatError } from './formatError';
export type { GraphQLFormattedError } from './formatError';
3 changes: 3 additions & 0 deletions src/index.js
Expand Up @@ -174,6 +174,9 @@ export type {
export {
Source,
getLocation,
// Print source location
printLocation,
printSourceLocation,
// Lex
createLexer,
TokenKind,
Expand Down
37 changes: 37 additions & 0 deletions src/language/__tests__/printLocation-test.js
@@ -0,0 +1,37 @@
// @flow strict

import { expect } from 'chai';
import { describe, it } from 'mocha';

import dedent from '../../jsutils/dedent';
import { Source } from '../../language';
import { printSourceLocation } from '../printLocation';

describe('printLocation', () => {
it('prints single digit line number with no padding', () => {
const result = printSourceLocation(
new Source('*', 'Test', { line: 9, column: 1 }),
{ line: 1, column: 1 },
);

expect(result + '\n').to.equal(dedent`
Test:9:1
9: *
^
`);
});

it('prints an line numbers with correct padding', () => {
const result = printSourceLocation(
new Source('*\n', 'Test', { line: 9, column: 1 }),
{ line: 1, column: 1 },
);

expect(result + '\n').to.equal(dedent`
Test:9:1
9: *
^
10:
`);
});
});
2 changes: 2 additions & 0 deletions src/language/index.js
Expand Up @@ -5,6 +5,8 @@ export { Source } from './source';
export { getLocation } from './location';
export type { SourceLocation } from './location';

export { printLocation, printSourceLocation } from './printLocation';

export { Kind } from './kinds';
export type { KindEnum } from './kinds';

Expand Down
49 changes: 15 additions & 34 deletions src/error/printError.js → src/language/printLocation.js
@@ -1,54 +1,35 @@
// @flow strict

import { type SourceLocation, getLocation } from '../language/location';
import { type Source } from '../language/source';
import { type GraphQLError } from './GraphQLError';
import { type Location } from '../language/ast';
import { type SourceLocation, getLocation } from '../language/location';

/**
* Prints a GraphQLError to a string, representing useful location information
* about the error's position in the source.
* Render a helpful description of the location in the GraphQL Source document.
*/
export function printError(error: GraphQLError): string {
const printedLocations = [];
if (error.nodes) {
for (const node of error.nodes) {
if (node.loc) {
printedLocations.push(
highlightSourceAtLocation(
node.loc.source,
getLocation(node.loc.source, node.loc.start),
),
);
}
}
} else if (error.source && error.locations) {
const source = error.source;
for (const location of error.locations) {
printedLocations.push(highlightSourceAtLocation(source, location));
}
}
return printedLocations.length === 0
? error.message
: [error.message, ...printedLocations].join('\n\n');
export function printLocation(location: Location): string {
return printSourceLocation(
location.source,
getLocation(location.source, location.start),
);
}

/**
* Render a helpful description of the location of the error in the GraphQL
* Source document.
* Render a helpful description of the location in the GraphQL Source document.
*/
function highlightSourceAtLocation(
export function printSourceLocation(
source: Source,
location: SourceLocation,
sourceLocation: SourceLocation,
): string {
const firstLineColumnOffset = source.locationOffset.column - 1;
const body = whitespace(firstLineColumnOffset) + source.body;

const lineIndex = location.line - 1;
const lineIndex = sourceLocation.line - 1;
const lineOffset = source.locationOffset.line - 1;
const lineNum = location.line + lineOffset;
const lineNum = sourceLocation.line + lineOffset;

const columnOffset = location.line === 1 ? firstLineColumnOffset : 0;
const columnNum = location.column + columnOffset;
const columnOffset = sourceLocation.line === 1 ? firstLineColumnOffset : 0;
const columnNum = sourceLocation.column + columnOffset;

const lines = body.split(/\r\n|[\n\r]/g);
return (
Expand Down

0 comments on commit a9a21f3

Please sign in to comment.