blockString.js.flow 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. // @flow strict
  2. /**
  3. * Produces the value of a block string from its parsed raw value, similar to
  4. * CoffeeScript's block string, Python's docstring trim or Ruby's strip_heredoc.
  5. *
  6. * This implements the GraphQL spec's BlockStringValue() static algorithm.
  7. */
  8. export function dedentBlockStringValue(rawString: string): string {
  9. // Expand a block string's raw value into independent lines.
  10. const lines = rawString.split(/\r\n|[\n\r]/g);
  11. // Remove common indentation from all lines but first.
  12. const commonIndent = getBlockStringIndentation(lines);
  13. if (commonIndent !== 0) {
  14. for (let i = 1; i < lines.length; i++) {
  15. lines[i] = lines[i].slice(commonIndent);
  16. }
  17. }
  18. // Remove leading and trailing blank lines.
  19. while (lines.length > 0 && isBlank(lines[0])) {
  20. lines.shift();
  21. }
  22. while (lines.length > 0 && isBlank(lines[lines.length - 1])) {
  23. lines.pop();
  24. }
  25. // Return a string of the lines joined with U+000A.
  26. return lines.join('\n');
  27. }
  28. // @internal
  29. export function getBlockStringIndentation(
  30. lines: $ReadOnlyArray<string>,
  31. ): number {
  32. let commonIndent = null;
  33. for (let i = 1; i < lines.length; i++) {
  34. const line = lines[i];
  35. const indent = leadingWhitespace(line);
  36. if (indent === line.length) {
  37. continue; // skip empty lines
  38. }
  39. if (commonIndent === null || indent < commonIndent) {
  40. commonIndent = indent;
  41. if (commonIndent === 0) {
  42. break;
  43. }
  44. }
  45. }
  46. return commonIndent === null ? 0 : commonIndent;
  47. }
  48. function leadingWhitespace(str) {
  49. let i = 0;
  50. while (i < str.length && (str[i] === ' ' || str[i] === '\t')) {
  51. i++;
  52. }
  53. return i;
  54. }
  55. function isBlank(str) {
  56. return leadingWhitespace(str) === str.length;
  57. }
  58. /**
  59. * Print a block string in the indented block form by adding a leading and
  60. * trailing blank line. However, if a block string starts with whitespace and is
  61. * a single-line, adding a leading blank line would strip that whitespace.
  62. */
  63. export function printBlockString(
  64. value: string,
  65. indentation?: string = '',
  66. preferMultipleLines?: boolean = false,
  67. ): string {
  68. const isSingleLine = value.indexOf('\n') === -1;
  69. const hasLeadingSpace = value[0] === ' ' || value[0] === '\t';
  70. const hasTrailingQuote = value[value.length - 1] === '"';
  71. const printAsMultipleLines =
  72. !isSingleLine || hasTrailingQuote || preferMultipleLines;
  73. let result = '';
  74. // Format a multi-line block quote to account for leading space.
  75. if (printAsMultipleLines && !(isSingleLine && hasLeadingSpace)) {
  76. result += '\n' + indentation;
  77. }
  78. result += indentation ? value.replace(/\n/g, '\n' + indentation) : value;
  79. if (printAsMultipleLines) {
  80. result += '\n';
  81. }
  82. return '"""' + result.replace(/"""/g, '\\"""') + '"""';
  83. }