@speclynx/apidom-parser-adapter-yaml-1-2 6.0.6 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/dist/apidom-parser-adapter-yaml-1-2.browser.js +445 -1203
- package/dist/apidom-parser-adapter-yaml-1-2.browser.min.js +1 -1
- package/package.json +6 -7
- package/src/tree-sitter/lexical-analysis/index.cjs +1 -1
- package/src/tree-sitter/lexical-analysis/index.mjs +1 -1
- package/src/tree-sitter/syntactic-analysis/CstTransformer.cjs +113 -9
- package/src/tree-sitter/syntactic-analysis/CstTransformer.mjs +113 -9
- package/src/tree-sitter/syntactic-analysis/YamlAstTransformer.cjs +110 -20
- package/src/tree-sitter/syntactic-analysis/YamlAstTransformer.mjs +111 -21
- package/src/tree-sitter/syntactic-analysis/ast/anchors-aliases/ReferenceManager.cjs +17 -1
- package/src/tree-sitter/syntactic-analysis/ast/anchors-aliases/ReferenceManager.mjs +17 -1
- package/src/tree-sitter/syntactic-analysis/ast/nodes/YamlScalar.cjs +3 -0
- package/src/tree-sitter/syntactic-analysis/ast/nodes/YamlScalar.mjs +3 -0
- package/src/tree-sitter/syntactic-analysis/ast/nodes/YamlStream.cjs +3 -0
- package/src/tree-sitter/syntactic-analysis/ast/nodes/YamlStream.mjs +3 -0
- package/src/tree-sitter/syntactic-analysis/ast/schemas/canonical-format.cjs +144 -51
- package/src/tree-sitter/syntactic-analysis/ast/schemas/canonical-format.mjs +146 -53
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ParseResultElement, AnnotationElement, CommentElement, MemberElement, ObjectElement, ArrayElement, isPrimitiveElement, Namespace, SourceMapElement } from '@speclynx/apidom-datamodel';
|
|
1
|
+
import { ParseResultElement, AnnotationElement, CommentElement, MemberElement, ObjectElement, ArrayElement, StringElement, NullElement, isPrimitiveElement, isNullElement, includesClasses, Namespace, SourceMapElement, StyleElement } from '@speclynx/apidom-datamodel';
|
|
2
2
|
import { isComment, isDocument } from "./ast/nodes/predicates.mjs";
|
|
3
3
|
import { YamlStyle, YamlStyleGroup } from "./ast/nodes/YamlStyle.mjs"; // Transform context passed through transformation
|
|
4
4
|
// Node with type property and flat position properties
|
|
@@ -125,7 +125,13 @@ const applyComments = (elements, comments) => {
|
|
|
125
125
|
const detectFlowPadding = node => {
|
|
126
126
|
const firstChild = (node.children || [])[0];
|
|
127
127
|
if (firstChild && typeof node.startCharacter === 'number' && typeof firstChild.startCharacter === 'number') {
|
|
128
|
-
|
|
128
|
+
// a sequence item starts with its tag or anchor, which aren't part of its node's position
|
|
129
|
+
const {
|
|
130
|
+
tag,
|
|
131
|
+
anchor
|
|
132
|
+
} = firstChild;
|
|
133
|
+
const firstStart = Math.min(firstChild.startCharacter, tag?.startCharacter ?? Infinity, anchor?.startCharacter ?? Infinity);
|
|
134
|
+
return firstStart - node.startCharacter > 1;
|
|
129
135
|
}
|
|
130
136
|
return true;
|
|
131
137
|
};
|
|
@@ -139,12 +145,25 @@ const buildYamlStyle = (node, ctx, extras) => {
|
|
|
139
145
|
if (ctx.flowCollectionPadding !== null) {
|
|
140
146
|
yamlStyle.flowCollectionPadding = ctx.flowCollectionPadding;
|
|
141
147
|
}
|
|
148
|
+
if (ctx.indentSeq !== null) {
|
|
149
|
+
yamlStyle.indentSeq = ctx.indentSeq;
|
|
150
|
+
}
|
|
142
151
|
if (node.comment) {
|
|
143
152
|
yamlStyle.comment = node.comment;
|
|
144
153
|
}
|
|
145
154
|
if (node.commentBefore) {
|
|
146
155
|
yamlStyle.commentBefore = node.commentBefore;
|
|
147
156
|
}
|
|
157
|
+
// an untagged node has the non-specific tag '?' when it's a plain scalar and '!' otherwise
|
|
158
|
+
// (YAML 1.2 §6.9.1); an explicit '!' on any other node means the same as no tag
|
|
159
|
+
const tag = node.tag?.explicitName;
|
|
160
|
+
if (tag !== undefined && tag !== (node.style === YamlStyle.Plain ? '?' : '!')) {
|
|
161
|
+
yamlStyle.tag = tag;
|
|
162
|
+
}
|
|
163
|
+
// the anchor's name, without the & indicator
|
|
164
|
+
if (node.anchor) {
|
|
165
|
+
yamlStyle.anchor = node.anchor.name.slice(1);
|
|
166
|
+
}
|
|
148
167
|
if (extras) {
|
|
149
168
|
for (const [key, value] of Object.entries(extras)) {
|
|
150
169
|
if (value !== undefined) yamlStyle[key] = value;
|
|
@@ -238,13 +257,35 @@ const transformChildren = (children, ctx) => {
|
|
|
238
257
|
|
|
239
258
|
// stream comments that precede the first document, joined; null when there are none
|
|
240
259
|
// (stream children are in source order, so everything before the document node qualifies)
|
|
241
|
-
|
|
242
|
-
|
|
260
|
+
// stream comments around the first document: before it, on its last line and on the lines after it,
|
|
261
|
+
// up to the next document
|
|
262
|
+
const collectStreamComments = node => {
|
|
263
|
+
const comments = {
|
|
264
|
+
before: [],
|
|
265
|
+
inline: [],
|
|
266
|
+
after: []
|
|
267
|
+
};
|
|
268
|
+
let root;
|
|
269
|
+
const collect = comment => {
|
|
270
|
+
const text = stripCommentHash(comment.content);
|
|
271
|
+
if (!root) comments.before.push(text);else if (comment.startLine === root.endLine) comments.inline.push(text);else comments.after.push(text);
|
|
272
|
+
};
|
|
243
273
|
for (const child of node.children) {
|
|
244
|
-
if (isDocument(child))
|
|
245
|
-
|
|
274
|
+
if (isDocument(child)) {
|
|
275
|
+
if (root) break;
|
|
276
|
+
// comments after an explicit document start (---) or before an explicit document end (...)
|
|
277
|
+
// are the document's own children, around its root node
|
|
278
|
+
const children = child.children || [];
|
|
279
|
+
const rootIndex = children.findIndex(c => !isComment(c));
|
|
280
|
+
if (rootIndex === -1) break;
|
|
281
|
+
children.slice(0, rootIndex).filter(isComment).forEach(collect);
|
|
282
|
+
root = children[rootIndex];
|
|
283
|
+
children.slice(rootIndex + 1).filter(isComment).forEach(collect);
|
|
284
|
+
} else if (isComment(child)) {
|
|
285
|
+
collect(child);
|
|
286
|
+
}
|
|
246
287
|
}
|
|
247
|
-
return
|
|
288
|
+
return comments;
|
|
248
289
|
};
|
|
249
290
|
|
|
250
291
|
// Stream: Wraps transformed children in ParseResultElement
|
|
@@ -269,13 +310,32 @@ const transformStream = (node, ctx) => {
|
|
|
269
310
|
const resultElement = elements[0];
|
|
270
311
|
resultElement.classes.push('result');
|
|
271
312
|
|
|
272
|
-
// stream comments before the first document belong above the result element
|
|
273
|
-
// capture them
|
|
313
|
+
// stream comments before the first document belong above the result element, ones on its last
|
|
314
|
+
// line to it and ones on later lines to the document; capture them so style round-trips can
|
|
315
|
+
// re-emit them
|
|
274
316
|
if (ctx.style) {
|
|
275
|
-
const
|
|
276
|
-
|
|
317
|
+
const {
|
|
318
|
+
before,
|
|
319
|
+
inline,
|
|
320
|
+
after
|
|
321
|
+
} = collectStreamComments(node);
|
|
322
|
+
if (before.length > 0) {
|
|
277
323
|
const existing = getYamlStyleProp(resultElement, 'commentBefore');
|
|
278
|
-
|
|
324
|
+
const text = before.join('\n');
|
|
325
|
+
setYamlStyleProp(resultElement, 'commentBefore', typeof existing === 'string' ? `${text}\n${existing}` : text);
|
|
326
|
+
}
|
|
327
|
+
if (inline.length > 0 && typeof getYamlStyleProp(resultElement, 'comment') !== 'string') {
|
|
328
|
+
setYamlStyleProp(resultElement, 'comment', inline.join('\n'));
|
|
329
|
+
}
|
|
330
|
+
// comments promoted out of the root's last nested collection have no next sibling to precede
|
|
331
|
+
// and end the root, so they come before the stream comments after it
|
|
332
|
+
after.unshift(...ctx.promotedComments);
|
|
333
|
+
if (after.length > 0) {
|
|
334
|
+
setYamlStyleProp(resultElement, 'documentComment', after.join('\n'));
|
|
335
|
+
}
|
|
336
|
+
// line breaks other than LF, which the YAML library writes
|
|
337
|
+
if (node.lineBreak === '\r\n') {
|
|
338
|
+
setYamlStyleProp(resultElement, 'lineBreak', node.lineBreak);
|
|
279
339
|
}
|
|
280
340
|
}
|
|
281
341
|
}
|
|
@@ -322,7 +382,9 @@ const transformCollection = (element, node, ctx) => {
|
|
|
322
382
|
element._content = childElements; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
323
383
|
applyComments(childElements, comments);
|
|
324
384
|
element.style = buildYamlStyle(node, ctx, {
|
|
325
|
-
comment: comments.trailing
|
|
385
|
+
comment: comments.trailing,
|
|
386
|
+
// a single pair written directly in a flow sequence (e.g. [a: 1])
|
|
387
|
+
singlePair: node.style === YamlStyle.SinglePair || undefined
|
|
326
388
|
});
|
|
327
389
|
if (comments.outOfScope.length > 0) {
|
|
328
390
|
ctx.promotedComments.push(...comments.outOfScope);
|
|
@@ -343,12 +405,34 @@ const transformMapping = (node, ctx) => {
|
|
|
343
405
|
// KeyValuePair: Transforms to MemberElement
|
|
344
406
|
const transformKeyValuePair = (node, ctx) => {
|
|
345
407
|
const element = new MemberElement();
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
409
|
+
const {
|
|
410
|
+
key,
|
|
411
|
+
value
|
|
412
|
+
} = node;
|
|
413
|
+
if (ctx.style && ctx.indentSeq === null && value?.type === 'sequence') {
|
|
414
|
+
const {
|
|
415
|
+
styleGroup,
|
|
416
|
+
startCharacter
|
|
417
|
+
} = value;
|
|
418
|
+
if (styleGroup === YamlStyleGroup.Block) {
|
|
419
|
+
ctx.indentSeq = startCharacter > node.startCharacter;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
346
422
|
|
|
347
423
|
// Transform key and value (these are dynamically defined properties)
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
424
|
+
let keyResult = transform(key, ctx);
|
|
425
|
+
const valueResult = transform(value, ctx);
|
|
426
|
+
|
|
427
|
+
// an empty key is a null in YAML, but a JavaScript property name can't be null, so it's the empty
|
|
428
|
+
// string it's named by, marked as an empty key
|
|
429
|
+
if (isNullElement(keyResult) && includesClasses(keyResult, ['yaml-e-node'])) {
|
|
430
|
+
const emptyKey = new StringElement('', keyResult.meta, keyResult.attributes);
|
|
431
|
+
emptyKey.classes.push('yaml-e-key');
|
|
432
|
+
SourceMapElement.transfer(keyResult, emptyKey);
|
|
433
|
+
StyleElement.transfer(keyResult, emptyKey);
|
|
434
|
+
keyResult = emptyKey;
|
|
435
|
+
}
|
|
352
436
|
|
|
353
437
|
// @ts-ignore
|
|
354
438
|
element.content.key = keyResult;
|
|
@@ -376,10 +460,12 @@ const transformSequence = (node, ctx) => {
|
|
|
376
460
|
|
|
377
461
|
// Scalar: Converts to Element via namespace
|
|
378
462
|
const transformScalar = (node, ctx) => {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
463
|
+
// an empty node (YAML 1.2 §7.2) is a null unless a tag other than the null tag makes it
|
|
464
|
+
// something else (e.g. !!str makes it an empty string); its classes tell it from an explicit null
|
|
465
|
+
const tag = node.tag?.explicitName;
|
|
466
|
+
const isEmptyNode = node.content === '' && node.style === YamlStyle.Plain && [undefined, '?', '!!null', '!<tag:yaml.org,2002:null>'].includes(tag);
|
|
467
|
+
const element = isEmptyNode ? new NullElement() : ctx.namespace.toElement(node.content);
|
|
468
|
+
if (isEmptyNode) {
|
|
383
469
|
element.classes.push('yaml-e-node');
|
|
384
470
|
element.classes.push('yaml-e-scalar');
|
|
385
471
|
}
|
|
@@ -390,6 +476,9 @@ const transformScalar = (node, ctx) => {
|
|
|
390
476
|
if (node.rawContent !== undefined) {
|
|
391
477
|
extras.rawContent = node.rawContent;
|
|
392
478
|
}
|
|
479
|
+
if (node.alias !== undefined) {
|
|
480
|
+
extras.alias = node.alias;
|
|
481
|
+
}
|
|
393
482
|
element.style = buildYamlStyle(node, ctx, extras);
|
|
394
483
|
}
|
|
395
484
|
maybeAddSourceMap(node, element, ctx);
|
|
@@ -467,6 +556,7 @@ export const transformYamlAstToApiDOM = (yamlAst, {
|
|
|
467
556
|
processedDocumentCount: 0,
|
|
468
557
|
indent: 2,
|
|
469
558
|
flowCollectionPadding: null,
|
|
559
|
+
indentSeq: null,
|
|
470
560
|
promotedComments: []
|
|
471
561
|
};
|
|
472
562
|
const rootNode = yamlAst.rootNode;
|
|
@@ -15,11 +15,27 @@ class ReferenceManager {
|
|
|
15
15
|
// Anchor registration - currently a no-op as aliases resolve to scalars
|
|
16
16
|
}
|
|
17
17
|
resolveAlias(alias) {
|
|
18
|
-
|
|
18
|
+
const {
|
|
19
|
+
startLine,
|
|
20
|
+
startCharacter,
|
|
21
|
+
startOffset,
|
|
22
|
+
endLine,
|
|
23
|
+
endCharacter,
|
|
24
|
+
endOffset
|
|
25
|
+
} = alias;
|
|
26
|
+
const scalar = new _YamlScalar.default({
|
|
19
27
|
content: alias.content,
|
|
28
|
+
startLine,
|
|
29
|
+
startCharacter,
|
|
30
|
+
startOffset,
|
|
31
|
+
endLine,
|
|
32
|
+
endCharacter,
|
|
33
|
+
endOffset,
|
|
20
34
|
style: _YamlStyle.YamlStyle.Plain,
|
|
21
35
|
styleGroup: _YamlStyle.YamlStyleGroup.Flow
|
|
22
36
|
});
|
|
37
|
+
scalar.alias = alias.content.slice(1);
|
|
38
|
+
return scalar;
|
|
23
39
|
}
|
|
24
40
|
}
|
|
25
41
|
var _default = exports.default = ReferenceManager;
|
|
@@ -8,11 +8,27 @@ class ReferenceManager {
|
|
|
8
8
|
// Anchor registration - currently a no-op as aliases resolve to scalars
|
|
9
9
|
}
|
|
10
10
|
resolveAlias(alias) {
|
|
11
|
-
|
|
11
|
+
const {
|
|
12
|
+
startLine,
|
|
13
|
+
startCharacter,
|
|
14
|
+
startOffset,
|
|
15
|
+
endLine,
|
|
16
|
+
endCharacter,
|
|
17
|
+
endOffset
|
|
18
|
+
} = alias;
|
|
19
|
+
const scalar = new YamlScalar({
|
|
12
20
|
content: alias.content,
|
|
21
|
+
startLine,
|
|
22
|
+
startCharacter,
|
|
23
|
+
startOffset,
|
|
24
|
+
endLine,
|
|
25
|
+
endCharacter,
|
|
26
|
+
endOffset,
|
|
13
27
|
style: YamlStyle.Plain,
|
|
14
28
|
styleGroup: YamlStyleGroup.Flow
|
|
15
29
|
});
|
|
30
|
+
scalar.alias = alias.content.slice(1);
|
|
31
|
+
return scalar;
|
|
16
32
|
}
|
|
17
33
|
}
|
|
18
34
|
export default ReferenceManager;
|
|
@@ -12,6 +12,9 @@ var _predicates = require("./predicates.cjs");
|
|
|
12
12
|
*/
|
|
13
13
|
class YamlStream extends _Node.default {
|
|
14
14
|
static type = 'stream';
|
|
15
|
+
|
|
16
|
+
// the line break the stream's first line ends with
|
|
17
|
+
lineBreak;
|
|
15
18
|
}
|
|
16
19
|
Object.defineProperty(YamlStream.prototype, 'content', {
|
|
17
20
|
get() {
|
|
@@ -5,6 +5,9 @@ import { isDocument, isComment } from "./predicates.mjs";
|
|
|
5
5
|
*/
|
|
6
6
|
class YamlStream extends Node {
|
|
7
7
|
static type = 'stream';
|
|
8
|
+
|
|
9
|
+
// the line break the stream's first line ends with
|
|
10
|
+
lineBreak;
|
|
8
11
|
}
|
|
9
12
|
Object.defineProperty(YamlStream.prototype, 'content', {
|
|
10
13
|
get() {
|
|
@@ -6,15 +6,16 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.formatFlowSingleQuoted = exports.formatFlowPlain = exports.formatFlowDoubleQuoted = exports.formatBlockLiteral = exports.formatBlockFolded = void 0;
|
|
7
7
|
var _ramda = require("ramda");
|
|
8
8
|
var _ramdaAdjunct = require("ramda-adjunct");
|
|
9
|
-
var _unraw = require("unraw");
|
|
10
9
|
/**
|
|
11
10
|
* Helpers.
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
// indentation and chomping indicators may appear in either order (YAML 1.2 §8.1.1)
|
|
14
|
+
const blockStyleRegExp = /^(?<style>[|>])(?<indicators>[+-]?[0-9]?|[0-9][+-])(?:\s|$)/;
|
|
15
15
|
const getIndentationIndicator = content => {
|
|
16
16
|
const matches = content.match(blockStyleRegExp);
|
|
17
|
-
const
|
|
17
|
+
const indicators = (0, _ramda.pathOr)('', ['groups', 'indicators'], matches);
|
|
18
|
+
const indicator = indicators.replace(/[+-]/, '');
|
|
18
19
|
return (0, _ramdaAdjunct.isEmptyString)(indicator) ? undefined : parseInt(indicator, 10);
|
|
19
20
|
};
|
|
20
21
|
const getIndentation = content => {
|
|
@@ -25,30 +26,72 @@ const getIndentation = content => {
|
|
|
25
26
|
return (0, _ramdaAdjunct.repeatStr)(' ', explicitIndentationIndicator);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
// we assume indentation indicator from first line
|
|
29
|
-
|
|
29
|
+
// we assume indentation indicator from first non-empty line (YAML 1.2 §8.1.1.1);
|
|
30
|
+
// with no non-empty line, the first line is used so that its spaces are still stripped
|
|
31
|
+
const lines = (0, _ramda.tail)(content.split('\n'));
|
|
32
|
+
const firstLine = lines.find(line => !/^ *\r?$/.test(line)) ?? (0, _ramda.pathOr)('', [0], lines);
|
|
30
33
|
const implicitIndentationIndicator = (0, _ramda.pathOr)(0, ['groups', 'indentation', 'length'], firstLine.match(/^(?<indentation>[ ]*)/));
|
|
31
34
|
return (0, _ramdaAdjunct.repeatStr)(' ', implicitIndentationIndicator);
|
|
32
35
|
};
|
|
36
|
+
|
|
37
|
+
// splits a block scalar body into deindented content lines and trailing lines (YAML 1.2 §8.1.1.2):
|
|
38
|
+
// trailing lines are the whitespace-only lines after the last content line, minus any up to and
|
|
39
|
+
// including the last one indented deeper than the block, which are content; without any content
|
|
40
|
+
// line, all lines are trailing
|
|
41
|
+
const splitBlockScalar = (content, chomping) => {
|
|
42
|
+
const indentation = getIndentation(content).length;
|
|
43
|
+
const normalized = normalizeLineBreaks(content);
|
|
44
|
+
const body = normalized.includes('\n') ? normalized.slice(normalized.indexOf('\n') + 1) : '';
|
|
45
|
+
const lines = body === '' ? [] : body.split('\n');
|
|
46
|
+
const deindent = line => line.slice(Math.min(indentation, line.search(/[^ ]|$/)));
|
|
47
|
+
|
|
48
|
+
// without keep chomping, trailing whitespace-only lines not indented deeper than the first
|
|
49
|
+
// content line don't belong to the block scalar
|
|
50
|
+
const firstContent = lines.find(line => /[^ ]/.test(line));
|
|
51
|
+
if (chomping !== '+' && firstContent !== undefined) {
|
|
52
|
+
const contentIndentation = firstContent.search(/[^ ]/);
|
|
53
|
+
while (/^ *$/.test(lines.at(-1)) && lines.at(-1).length <= contentIndentation) lines.pop();
|
|
54
|
+
}
|
|
55
|
+
let chompStart = lines.length;
|
|
56
|
+
while (chompStart > 0 && /^ *$/.test(lines[chompStart - 1])) chompStart -= 1;
|
|
57
|
+
if (chompStart === 0) return {
|
|
58
|
+
lines: [],
|
|
59
|
+
trailing: lines.map(deindent)
|
|
60
|
+
};
|
|
61
|
+
for (let i = lines.length - 1; i >= chompStart; i -= 1) {
|
|
62
|
+
if (lines[i].length > indentation) {
|
|
63
|
+
chompStart = i + 1;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
lines: lines.slice(0, chompStart).map(deindent),
|
|
69
|
+
trailing: lines.slice(chompStart).map(deindent)
|
|
70
|
+
};
|
|
71
|
+
};
|
|
33
72
|
const getChompingIndicator = content => {
|
|
34
73
|
const matches = content.match(blockStyleRegExp);
|
|
35
|
-
const
|
|
74
|
+
const indicators = (0, _ramda.pathOr)('', ['groups', 'indicators'], matches);
|
|
75
|
+
const indicator = indicators.replace(/[0-9]/, '');
|
|
36
76
|
return (0, _ramdaAdjunct.isEmptyString)(indicator) ? undefined : indicator;
|
|
37
77
|
};
|
|
38
|
-
const chomp = (indicator,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
const chomp = (indicator, value, {
|
|
79
|
+
lines,
|
|
80
|
+
trailing
|
|
81
|
+
}) => {
|
|
82
|
+
// a block without content is empty, unless keep chomping preserves its line breaks
|
|
83
|
+
if (lines.length === 0) {
|
|
84
|
+
return indicator === '+' && trailing.length > 0 ? '\n'.repeat(Math.max(1, trailing.length - 1)) : '';
|
|
42
85
|
}
|
|
43
86
|
// strip (no newline at end)
|
|
44
|
-
if (indicator === '-')
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
// keep (all newlines from end)
|
|
87
|
+
if (indicator === '-') return value;
|
|
88
|
+
// keep (trailing lines and a newline at end)
|
|
48
89
|
if (indicator === '+') {
|
|
49
|
-
|
|
90
|
+
const kept = value + trailing.map(line => `\n${line}`).join('');
|
|
91
|
+
return kept.endsWith('\n') ? kept : `${kept}\n`;
|
|
50
92
|
}
|
|
51
|
-
|
|
93
|
+
// clip (single newline at end)
|
|
94
|
+
return `${value}\n`;
|
|
52
95
|
};
|
|
53
96
|
|
|
54
97
|
/**
|
|
@@ -58,56 +101,108 @@ const chomp = (indicator, content) => {
|
|
|
58
101
|
// @ts-ignore
|
|
59
102
|
const normalizeLineBreaks = val => val.replace(/\r\n/g, '\n');
|
|
60
103
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
104
|
+
// folds the line breaks of a flow scalar (YAML 1.2 §6.5, §7.3):
|
|
105
|
+
// white space around a line break is dropped, a single line break becomes a space and every
|
|
106
|
+
// further one a line break; with escapes (double-quoted style), escape sequences are left for
|
|
107
|
+
// unescaping and an escaped line break is dropped together with the next line's indentation
|
|
108
|
+
const foldFlowLines = (0, _ramda.curry)((escapes, text) => text.replace(
|
|
109
|
+
// a white space run not followed by a line break is consumed whole, which keeps the search
|
|
110
|
+
// linear instead of rescanning the run from every position in it
|
|
111
|
+
escapes ? /\\(?:\n[ \t]*|[^])|[ \t]*\n(?:[ \t]*\n)*[ \t]*|[ \t]+/g : /[ \t]*\n(?:[ \t]*\n)*[ \t]*|[ \t]+/g, match => {
|
|
112
|
+
if (match.startsWith('\\')) return match[1] === '\n' ? '' : match;
|
|
113
|
+
if (!match.includes('\n')) return match;
|
|
114
|
+
const lineBreaks = match.split('\n').length - 1;
|
|
115
|
+
return lineBreaks === 1 ? ' ' : '\n'.repeat(lineBreaks - 1);
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
// folds the line break between two lines into a space; empty lines between them stay as line
|
|
119
|
+
// breaks, and a line break next to a more-indented line isn't folded (YAML 1.2 §6.5)
|
|
120
|
+
const foldLines = text => {
|
|
121
|
+
const parts = text.split(/\n(\n*)/); // lines alternating with the empty lines between them
|
|
122
|
+
let folded = parts[0];
|
|
123
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
124
|
+
const moreIndented = /^[ \t]/.test(parts[i - 1]) || /^[ \t]/.test(parts[i + 1]);
|
|
125
|
+
folded += (moreIndented ? `\n${parts[i]}` : parts[i] || ' ') + parts[i + 1];
|
|
126
|
+
}
|
|
127
|
+
return folded;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// escaped characters of YAML 1.2 §5.7, productions [42] to [58]
|
|
131
|
+
const escapedCharacters = {
|
|
132
|
+
'0': '\u0000',
|
|
133
|
+
// null
|
|
134
|
+
a: '\u0007',
|
|
135
|
+
// bell
|
|
136
|
+
b: '\u0008',
|
|
137
|
+
// backspace
|
|
138
|
+
t: '\u0009',
|
|
139
|
+
// horizontal tab
|
|
140
|
+
'\t': '\u0009',
|
|
141
|
+
// horizontal tab
|
|
142
|
+
n: '\u000a',
|
|
143
|
+
// line feed
|
|
144
|
+
v: '\u000b',
|
|
145
|
+
// vertical tab
|
|
146
|
+
f: '\u000c',
|
|
147
|
+
// form feed
|
|
148
|
+
r: '\u000d',
|
|
149
|
+
// carriage return
|
|
150
|
+
e: '\u001b',
|
|
151
|
+
// escape
|
|
152
|
+
' ': '\u0020',
|
|
153
|
+
// space
|
|
154
|
+
'"': '\u0022',
|
|
155
|
+
// double quote
|
|
156
|
+
'/': '\u002f',
|
|
157
|
+
// slash
|
|
158
|
+
'\\': '\u005c',
|
|
159
|
+
// backslash
|
|
160
|
+
N: '\u0085',
|
|
161
|
+
// next line
|
|
162
|
+
_: '\u00a0',
|
|
163
|
+
// non-breaking space
|
|
164
|
+
L: '\u2028',
|
|
165
|
+
// line separator
|
|
166
|
+
P: '\u2029' // paragraph separator
|
|
76
167
|
};
|
|
168
|
+
|
|
169
|
+
// unescapes the escape sequences of a double-quoted scalar (YAML 1.2 §5.7); an escape sequence
|
|
170
|
+
// outside of the spec is kept as-is
|
|
171
|
+
const unescapeDoubleQuoted = text => text.replace(/\\(?:x([0-9a-fA-F]{2})|u([0-9a-fA-F]{4})|U([0-9a-fA-F]{8})|([^]))/g, (match, x, u, U, char) => {
|
|
172
|
+
const hex = x ?? u ?? U;
|
|
173
|
+
if (hex === undefined) return escapedCharacters[char] ?? match;
|
|
174
|
+
const codePoint = parseInt(hex, 16);
|
|
175
|
+
return codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : match;
|
|
176
|
+
});
|
|
77
177
|
const removeQuotes = (0, _ramda.curry)((quoteType, val) => val.replace(new RegExp(`^${quoteType}`), '').replace(new RegExp(`${quoteType}$`), ''));
|
|
78
178
|
|
|
79
179
|
/**
|
|
80
180
|
* Formats Flow Scalar Plain style.
|
|
81
181
|
* https://yaml.org/spec/1.2/spec.html#id2788859
|
|
82
182
|
*/
|
|
83
|
-
const formatFlowPlain = exports.formatFlowPlain = (0, _ramda.pipe)(normalizeLineBreaks, _ramda.trim,
|
|
183
|
+
const formatFlowPlain = exports.formatFlowPlain = (0, _ramda.pipe)(normalizeLineBreaks, _ramda.trim, foldFlowLines(false));
|
|
84
184
|
|
|
85
185
|
/**
|
|
86
186
|
* Formats Flow Scalar Single-Quoted style.
|
|
87
187
|
* https://yaml.org/spec/1.2/spec.html#id2788097
|
|
88
188
|
*/
|
|
89
189
|
|
|
90
|
-
const formatFlowSingleQuoted = exports.formatFlowSingleQuoted = (0, _ramda.pipe)(normalizeLineBreaks, _ramda.trim,
|
|
190
|
+
const formatFlowSingleQuoted = exports.formatFlowSingleQuoted = (0, _ramda.pipe)(normalizeLineBreaks, _ramda.trim, foldFlowLines(false), removeQuotes("'"), (0, _ramda.replace)(/''/g, "'"));
|
|
91
191
|
|
|
92
192
|
/**
|
|
93
193
|
* Formats Flow Scalar Double-Quoted style.
|
|
94
194
|
* https://yaml.org/spec/1.2/spec.html#id2787109
|
|
95
195
|
*/
|
|
96
|
-
const formatFlowDoubleQuoted = exports.formatFlowDoubleQuoted = (0, _ramda.pipe)(normalizeLineBreaks, _ramda.trim,
|
|
196
|
+
const formatFlowDoubleQuoted = exports.formatFlowDoubleQuoted = (0, _ramda.pipe)(normalizeLineBreaks, _ramda.trim, foldFlowLines(true), unescapeDoubleQuoted, removeQuotes('"'));
|
|
97
197
|
|
|
98
198
|
/**
|
|
99
199
|
* Formats Block Scalar Literal style.
|
|
100
200
|
* https://yaml.org/spec/1.2/spec.html#id2795688
|
|
101
201
|
*/
|
|
102
202
|
const formatBlockLiteral = content => {
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const lines = (0, _ramda.tail)(normalized.split('\n')); // first line only contains indicators
|
|
107
|
-
const transducer = (0, _ramda.compose)((0, _ramda.map)((0, _ramdaAdjunct.trimCharsStart)(indentation)), (0, _ramda.map)((0, _ramdaAdjunct.concatRight)('\n')));
|
|
108
|
-
// @ts-ignore
|
|
109
|
-
const deindented = (0, _ramda.transduce)(transducer, _ramda.concat, '', lines);
|
|
110
|
-
return chomp(chompingIndicator, deindented);
|
|
203
|
+
const chomping = getChompingIndicator(content);
|
|
204
|
+
const split = splitBlockScalar(content, chomping);
|
|
205
|
+
return chomp(chomping, split.lines.join('\n'), split);
|
|
111
206
|
};
|
|
112
207
|
|
|
113
208
|
/**
|
|
@@ -116,14 +211,12 @@ const formatBlockLiteral = content => {
|
|
|
116
211
|
*/
|
|
117
212
|
exports.formatBlockLiteral = formatBlockLiteral;
|
|
118
213
|
const formatBlockFolded = content => {
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const collapsed = collapseLineBreakToSpace(deindented);
|
|
127
|
-
return chomp(chompingIndicator, collapsed);
|
|
214
|
+
const chomping = getChompingIndicator(content);
|
|
215
|
+
const split = splitBlockScalar(content, chomping);
|
|
216
|
+
// leading line breaks are preserved as-is; folding applies only between content lines
|
|
217
|
+
const text = split.lines.join('\n');
|
|
218
|
+
const leadingLineBreaks = text.match(/^\n*/)[0];
|
|
219
|
+
const folded = leadingLineBreaks + foldLines(text.slice(leadingLineBreaks.length));
|
|
220
|
+
return chomp(chomping, folded, split);
|
|
128
221
|
};
|
|
129
222
|
exports.formatBlockFolded = formatBlockFolded;
|