blockString.mjs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import { isWhiteSpace } from './characterClasses.mjs';
  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. * @internal
  9. */
  10. export function dedentBlockStringLines(lines) {
  11. var _firstNonEmptyLine2;
  12. let commonIndent = Number.MAX_SAFE_INTEGER;
  13. let firstNonEmptyLine = null;
  14. let lastNonEmptyLine = -1;
  15. for (let i = 0; i < lines.length; ++i) {
  16. var _firstNonEmptyLine;
  17. const line = lines[i];
  18. const indent = leadingWhitespace(line);
  19. if (indent === line.length) {
  20. continue; // skip empty lines
  21. }
  22. firstNonEmptyLine =
  23. (_firstNonEmptyLine = firstNonEmptyLine) !== null &&
  24. _firstNonEmptyLine !== void 0
  25. ? _firstNonEmptyLine
  26. : i;
  27. lastNonEmptyLine = i;
  28. if (i !== 0 && indent < commonIndent) {
  29. commonIndent = indent;
  30. }
  31. }
  32. return lines // Remove common indentation from all lines but first.
  33. .map((line, i) => (i === 0 ? line : line.slice(commonIndent))) // Remove leading and trailing blank lines.
  34. .slice(
  35. (_firstNonEmptyLine2 = firstNonEmptyLine) !== null &&
  36. _firstNonEmptyLine2 !== void 0
  37. ? _firstNonEmptyLine2
  38. : 0,
  39. lastNonEmptyLine + 1,
  40. );
  41. }
  42. function leadingWhitespace(str) {
  43. let i = 0;
  44. while (i < str.length && isWhiteSpace(str.charCodeAt(i))) {
  45. ++i;
  46. }
  47. return i;
  48. }
  49. /**
  50. * @internal
  51. */
  52. export function isPrintableAsBlockString(value) {
  53. if (value === '') {
  54. return true; // empty string is printable
  55. }
  56. let isEmptyLine = true;
  57. let hasIndent = false;
  58. let hasCommonIndent = true;
  59. let seenNonEmptyLine = false;
  60. for (let i = 0; i < value.length; ++i) {
  61. switch (value.codePointAt(i)) {
  62. case 0x0000:
  63. case 0x0001:
  64. case 0x0002:
  65. case 0x0003:
  66. case 0x0004:
  67. case 0x0005:
  68. case 0x0006:
  69. case 0x0007:
  70. case 0x0008:
  71. case 0x000b:
  72. case 0x000c:
  73. case 0x000e:
  74. case 0x000f:
  75. return false;
  76. // Has non-printable characters
  77. case 0x000d:
  78. // \r
  79. return false;
  80. // Has \r or \r\n which will be replaced as \n
  81. case 10:
  82. // \n
  83. if (isEmptyLine && !seenNonEmptyLine) {
  84. return false; // Has leading new line
  85. }
  86. seenNonEmptyLine = true;
  87. isEmptyLine = true;
  88. hasIndent = false;
  89. break;
  90. case 9: // \t
  91. case 32:
  92. // <space>
  93. hasIndent || (hasIndent = isEmptyLine);
  94. break;
  95. default:
  96. hasCommonIndent && (hasCommonIndent = hasIndent);
  97. isEmptyLine = false;
  98. }
  99. }
  100. if (isEmptyLine) {
  101. return false; // Has trailing empty lines
  102. }
  103. if (hasCommonIndent && seenNonEmptyLine) {
  104. return false; // Has internal indent
  105. }
  106. return true;
  107. }
  108. /**
  109. * Print a block string in the indented block form by adding a leading and
  110. * trailing blank line. However, if a block string starts with whitespace and is
  111. * a single-line, adding a leading blank line would strip that whitespace.
  112. *
  113. * @internal
  114. */
  115. export function printBlockString(value, options) {
  116. const escapedValue = value.replace(/"""/g, '\\"""'); // Expand a block string's raw value into independent lines.
  117. const lines = escapedValue.split(/\r\n|[\n\r]/g);
  118. const isSingleLine = lines.length === 1; // If common indentation is found we can fix some of those cases by adding leading new line
  119. const forceLeadingNewLine =
  120. lines.length > 1 &&
  121. lines
  122. .slice(1)
  123. .every((line) => line.length === 0 || isWhiteSpace(line.charCodeAt(0))); // Trailing triple quotes just looks confusing but doesn't force trailing new line
  124. const hasTrailingTripleQuotes = escapedValue.endsWith('\\"""'); // Trailing quote (single or double) or slash forces trailing new line
  125. const hasTrailingQuote = value.endsWith('"') && !hasTrailingTripleQuotes;
  126. const hasTrailingSlash = value.endsWith('\\');
  127. const forceTrailingNewline = hasTrailingQuote || hasTrailingSlash;
  128. const printAsMultipleLines =
  129. !(options !== null && options !== void 0 && options.minimize) && // add leading and trailing new lines only if it improves readability
  130. (!isSingleLine ||
  131. value.length > 70 ||
  132. forceTrailingNewline ||
  133. forceLeadingNewLine ||
  134. hasTrailingTripleQuotes);
  135. let result = ''; // Format a multi-line block quote to account for leading space.
  136. const skipLeadingNewLine = isSingleLine && isWhiteSpace(value.charCodeAt(0));
  137. if ((printAsMultipleLines && !skipLeadingNewLine) || forceLeadingNewLine) {
  138. result += '\n';
  139. }
  140. result += escapedValue;
  141. if (printAsMultipleLines || forceTrailingNewline) {
  142. result += '\n';
  143. }
  144. return '"""' + result + '"""';
  145. }