|
| 1 | +/** |
| 2 | + * Copyright (c) 2015-present, Facebook, Inc. |
| 3 | + * |
| 4 | + * This source code is licensed under the MIT license found in the |
| 5 | + * LICENSE file in the root directory of this source tree. |
| 6 | + * |
| 7 | + * @flow |
| 8 | + */ |
| 9 | + |
| 10 | +import type { SourceLocation } from '../language/location'; |
| 11 | +import type { Source } from '../language/source'; |
| 12 | +import type { GraphQLError } from './GraphQLError'; |
| 13 | + |
| 14 | +/** |
| 15 | + * Prints a GraphQLError to a string, representing useful location information |
| 16 | + * about the error's position in the source. |
| 17 | + */ |
| 18 | +export function printError(error: GraphQLError): string { |
| 19 | + const source = error.source; |
| 20 | + const locations = error.locations || []; |
| 21 | + const printedLocations = locations.map( |
| 22 | + location => |
| 23 | + source |
| 24 | + ? highlightSourceAtLocation(source, location) |
| 25 | + : ` (${location.line}:${location.column})`, |
| 26 | + ); |
| 27 | + return error.message + printedLocations.join(''); |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * Render a helpful description of the location of the error in the GraphQL |
| 32 | + * Source document. |
| 33 | + */ |
| 34 | +function highlightSourceAtLocation( |
| 35 | + source: Source, |
| 36 | + location: SourceLocation, |
| 37 | +): string { |
| 38 | + const line = location.line; |
| 39 | + const lineOffset = source.locationOffset.line - 1; |
| 40 | + const columnOffset = getColumnOffset(source, location); |
| 41 | + const contextLine = line + lineOffset; |
| 42 | + const contextColumn = location.column + columnOffset; |
| 43 | + const prevLineNum = (contextLine - 1).toString(); |
| 44 | + const lineNum = contextLine.toString(); |
| 45 | + const nextLineNum = (contextLine + 1).toString(); |
| 46 | + const padLen = nextLineNum.length; |
| 47 | + const lines = source.body.split(/\r\n|[\n\r]/g); |
| 48 | + lines[0] = whitespace(source.locationOffset.column - 1) + lines[0]; |
| 49 | + return ( |
| 50 | + `\n\n${source.name} (${contextLine}:${contextColumn})\n` + |
| 51 | + (line >= 2 |
| 52 | + ? lpad(padLen, prevLineNum) + ': ' + lines[line - 2] + '\n' |
| 53 | + : '') + |
| 54 | + lpad(padLen, lineNum) + |
| 55 | + ': ' + |
| 56 | + lines[line - 1] + |
| 57 | + '\n' + |
| 58 | + whitespace(2 + padLen + contextColumn - 1) + |
| 59 | + '^\n' + |
| 60 | + (line < lines.length |
| 61 | + ? lpad(padLen, nextLineNum) + ': ' + lines[line] + '\n' |
| 62 | + : '') |
| 63 | + ); |
| 64 | +} |
| 65 | + |
| 66 | +function getColumnOffset(source: Source, location: SourceLocation): number { |
| 67 | + return location.line === 1 ? source.locationOffset.column - 1 : 0; |
| 68 | +} |
| 69 | + |
| 70 | +function whitespace(len: number): string { |
| 71 | + return Array(len + 1).join(' '); |
| 72 | +} |
| 73 | + |
| 74 | +function lpad(len: number, str: string): string { |
| 75 | + return whitespace(len - str.length) + str; |
| 76 | +} |
0 commit comments