-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathblockString.ts
169 lines (143 loc) · 4.45 KB
/
blockString.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import { isWhiteSpace } from './characterClasses.js';
/**
* 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: ReadonlyArray<string>,
): Array<string> {
let commonIndent = Number.MAX_SAFE_INTEGER;
let firstNonEmptyLine = null;
let lastNonEmptyLine = -1;
for (let i = 0; i < lines.length; ++i) {
const line = lines[i];
const indent = leadingWhitespace(line);
if (indent === line.length) {
continue; // skip empty lines
}
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(firstNonEmptyLine ?? 0, lastNonEmptyLine + 1)
);
}
function leadingWhitespace(str: string): number {
let i = 0;
while (i < str.length && isWhiteSpace(str.charCodeAt(i))) {
++i;
}
return i;
}
/**
* @internal
*/
export function isPrintableAsBlockString(value: string): boolean {
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 ||= isEmptyLine;
break;
default:
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: string,
options?: { minimize?: boolean },
): string {
const escapedValue = value.replaceAll('"""', '\\"""');
// 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?.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 + '"""';
}