You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
4.7 KiB
179 lines
4.7 KiB
4 months ago
|
import { isWhiteSpace } from './characterClasses.mjs';
|
||
|
/**
|
||
|
* Produces the value of a block string from its parsed raw value, similar to
|
||
|
* CoffeeScript's block string, Python's docstring trim or Ruby's strip_heredoc.
|
||
|
*
|
||
|
* This implements the GraphQL spec's BlockStringValue() static algorithm.
|
||
|
*
|
||
|
* @internal
|
||
|
*/
|
||
|
|
||
|
export function dedentBlockStringLines(lines) {
|
||
|
var _firstNonEmptyLine2;
|
||
|
|
||
|
let commonIndent = Number.MAX_SAFE_INTEGER;
|
||
|
let firstNonEmptyLine = null;
|
||
|
let lastNonEmptyLine = -1;
|
||
|
|
||
|
for (let i = 0; i < lines.length; ++i) {
|
||
|
var _firstNonEmptyLine;
|
||
|
|
||
|
const line = lines[i];
|
||
|
const indent = leadingWhitespace(line);
|
||
|
|
||
|
if (indent === line.length) {
|
||
|
continue; // skip empty lines
|
||
|
}
|
||
|
|
||
|
firstNonEmptyLine =
|
||
|
(_firstNonEmptyLine = firstNonEmptyLine) !== null &&
|
||
|
_firstNonEmptyLine !== void 0
|
||
|
? _firstNonEmptyLine
|
||
|
: i;
|
||
|
lastNonEmptyLine = i;
|
||
|
|
||
|
if (i !== 0 && indent < commonIndent) {
|
||
|
commonIndent = indent;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return lines // Remove common indentation from all lines but first.
|
||
|
.map((line, i) => (i === 0 ? line : line.slice(commonIndent))) // Remove leading and trailing blank lines.
|
||
|
.slice(
|
||
|
(_firstNonEmptyLine2 = firstNonEmptyLine) !== null &&
|
||
|
_firstNonEmptyLine2 !== void 0
|
||
|
? _firstNonEmptyLine2
|
||
|
: 0,
|
||
|
lastNonEmptyLine + 1,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function leadingWhitespace(str) {
|
||
|
let i = 0;
|
||
|
|
||
|
while (i < str.length && isWhiteSpace(str.charCodeAt(i))) {
|
||
|
++i;
|
||
|
}
|
||
|
|
||
|
return i;
|
||
|
}
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
|
||
|
export function isPrintableAsBlockString(value) {
|
||
|
if (value === '') {
|
||
|
return true; // empty string is printable
|
||
|
}
|
||
|
|
||
|
let isEmptyLine = true;
|
||
|
let hasIndent = false;
|
||
|
let hasCommonIndent = true;
|
||
|
let seenNonEmptyLine = false;
|
||
|
|
||
|
for (let i = 0; i < value.length; ++i) {
|
||
|
switch (value.codePointAt(i)) {
|
||
|
case 0x0000:
|
||
|
case 0x0001:
|
||
|
case 0x0002:
|
||
|
case 0x0003:
|
||
|
case 0x0004:
|
||
|
case 0x0005:
|
||
|
case 0x0006:
|
||
|
case 0x0007:
|
||
|
case 0x0008:
|
||
|
case 0x000b:
|
||
|
case 0x000c:
|
||
|
case 0x000e:
|
||
|
case 0x000f:
|
||
|
return false;
|
||
|
// Has non-printable characters
|
||
|
|
||
|
case 0x000d:
|
||
|
// \r
|
||
|
return false;
|
||
|
// Has \r or \r\n which will be replaced as \n
|
||
|
|
||
|
case 10:
|
||
|
// \n
|
||
|
if (isEmptyLine && !seenNonEmptyLine) {
|
||
|
return false; // Has leading new line
|
||
|
}
|
||
|
|
||
|
seenNonEmptyLine = true;
|
||
|
isEmptyLine = true;
|
||
|
hasIndent = false;
|
||
|
break;
|
||
|
|
||
|
case 9: // \t
|
||
|
|
||
|
case 32:
|
||
|
// <space>
|
||
|
hasIndent || (hasIndent = isEmptyLine);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
hasCommonIndent && (hasCommonIndent = hasIndent);
|
||
|
isEmptyLine = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (isEmptyLine) {
|
||
|
return false; // Has trailing empty lines
|
||
|
}
|
||
|
|
||
|
if (hasCommonIndent && seenNonEmptyLine) {
|
||
|
return false; // Has internal indent
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
/**
|
||
|
* Print a block string in the indented block form by adding a leading and
|
||
|
* trailing blank line. However, if a block string starts with whitespace and is
|
||
|
* a single-line, adding a leading blank line would strip that whitespace.
|
||
|
*
|
||
|
* @internal
|
||
|
*/
|
||
|
|
||
|
export function printBlockString(value, options) {
|
||
|
const escapedValue = value.replace(/"""/g, '\\"""'); // Expand a block string's raw value into independent lines.
|
||
|
|
||
|
const lines = escapedValue.split(/\r\n|[\n\r]/g);
|
||
|
const isSingleLine = lines.length === 1; // If common indentation is found we can fix some of those cases by adding leading new line
|
||
|
|
||
|
const forceLeadingNewLine =
|
||
|
lines.length > 1 &&
|
||
|
lines
|
||
|
.slice(1)
|
||
|
.every((line) => line.length === 0 || isWhiteSpace(line.charCodeAt(0))); // Trailing triple quotes just looks confusing but doesn't force trailing new line
|
||
|
|
||
|
const hasTrailingTripleQuotes = escapedValue.endsWith('\\"""'); // Trailing quote (single or double) or slash forces trailing new line
|
||
|
|
||
|
const hasTrailingQuote = value.endsWith('"') && !hasTrailingTripleQuotes;
|
||
|
const hasTrailingSlash = value.endsWith('\\');
|
||
|
const forceTrailingNewline = hasTrailingQuote || hasTrailingSlash;
|
||
|
const printAsMultipleLines =
|
||
|
!(options !== null && options !== void 0 && options.minimize) && // add leading and trailing new lines only if it improves readability
|
||
|
(!isSingleLine ||
|
||
|
value.length > 70 ||
|
||
|
forceTrailingNewline ||
|
||
|
forceLeadingNewLine ||
|
||
|
hasTrailingTripleQuotes);
|
||
|
let result = ''; // Format a multi-line block quote to account for leading space.
|
||
|
|
||
|
const skipLeadingNewLine = isSingleLine && isWhiteSpace(value.charCodeAt(0));
|
||
|
|
||
|
if ((printAsMultipleLines && !skipLeadingNewLine) || forceLeadingNewLine) {
|
||
|
result += '\n';
|
||
|
}
|
||
|
|
||
|
result += escapedValue;
|
||
|
|
||
|
if (printAsMultipleLines || forceTrailingNewline) {
|
||
|
result += '\n';
|
||
|
}
|
||
|
|
||
|
return '"""' + result + '"""';
|
||
|
}
|