printLocation.js.flow 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. // @flow strict
  2. import { type Location } from '../language/ast';
  3. import { type Source } from '../language/source';
  4. import { type SourceLocation, getLocation } from '../language/location';
  5. /**
  6. * Render a helpful description of the location in the GraphQL Source document.
  7. */
  8. export function printLocation(location: Location): string {
  9. return printSourceLocation(
  10. location.source,
  11. getLocation(location.source, location.start),
  12. );
  13. }
  14. /**
  15. * Render a helpful description of the location in the GraphQL Source document.
  16. */
  17. export function printSourceLocation(
  18. source: Source,
  19. sourceLocation: SourceLocation,
  20. ): string {
  21. const firstLineColumnOffset = source.locationOffset.column - 1;
  22. const body = whitespace(firstLineColumnOffset) + source.body;
  23. const lineIndex = sourceLocation.line - 1;
  24. const lineOffset = source.locationOffset.line - 1;
  25. const lineNum = sourceLocation.line + lineOffset;
  26. const columnOffset = sourceLocation.line === 1 ? firstLineColumnOffset : 0;
  27. const columnNum = sourceLocation.column + columnOffset;
  28. const locationStr = `${source.name}:${lineNum}:${columnNum}\n`;
  29. const lines = body.split(/\r\n|[\n\r]/g);
  30. const locationLine = lines[lineIndex];
  31. // Special case for minified documents
  32. if (locationLine.length > 120) {
  33. const sublineIndex = Math.floor(columnNum / 80);
  34. const sublineColumnNum = columnNum % 80;
  35. const sublines = [];
  36. for (let i = 0; i < locationLine.length; i += 80) {
  37. sublines.push(locationLine.slice(i, i + 80));
  38. }
  39. return (
  40. locationStr +
  41. printPrefixedLines([
  42. [`${lineNum}`, sublines[0]],
  43. ...sublines.slice(1, sublineIndex + 1).map(subline => ['', subline]),
  44. [' ', whitespace(sublineColumnNum - 1) + '^'],
  45. ['', sublines[sublineIndex + 1]],
  46. ])
  47. );
  48. }
  49. return (
  50. locationStr +
  51. printPrefixedLines([
  52. // Lines specified like this: ["prefix", "string"],
  53. [`${lineNum - 1}`, lines[lineIndex - 1]],
  54. [`${lineNum}`, locationLine],
  55. ['', whitespace(columnNum - 1) + '^'],
  56. [`${lineNum + 1}`, lines[lineIndex + 1]],
  57. ])
  58. );
  59. }
  60. function printPrefixedLines(lines: $ReadOnlyArray<[string, string]>): string {
  61. const existingLines = lines.filter(([_, line]) => line !== undefined);
  62. const padLen = Math.max(...existingLines.map(([prefix]) => prefix.length));
  63. return existingLines
  64. .map(
  65. ([prefix, line]) => lpad(padLen, prefix) + (line ? ' | ' + line : ' |'),
  66. )
  67. .join('\n');
  68. }
  69. function whitespace(len: number): string {
  70. return Array(len + 1).join(' ');
  71. }
  72. function lpad(len: number, str: string): string {
  73. return whitespace(len - str.length) + str;
  74. }