/
stringifyCollection.ts
120 lines (113 loc) · 3.71 KB
/
stringifyCollection.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
import { Collection } from '../nodes/Collection.js'
import { addComment } from '../stringify/addComment.js'
import { stringify, StringifyContext } from '../stringify/stringify.js'
import { isNode, isPair } from '../nodes/Node.js'
type StringifyNode = { comment: boolean; str: string }
interface StringifyCollectionOptions {
blockItem(node: StringifyNode): string
flowChars: { start: '{'; end: '}' } | { start: '['; end: ']' }
itemIndent: string
onChompKeep?: () => void
onComment?: () => void
}
export function stringifyCollection(
{ comment, flow, items }: Readonly<Collection>,
ctx: StringifyContext,
{
blockItem,
flowChars,
itemIndent,
onChompKeep,
onComment
}: StringifyCollectionOptions
) {
const { indent, indentStep } = ctx
const inFlow = flow || ctx.inFlow
if (inFlow) itemIndent += indentStep
ctx = Object.assign({}, ctx, { indent: itemIndent, inFlow, type: null })
let singleLineOutput = true
let chompKeep = false // flag for the preceding node's status
const nodes = items.reduce((nodes: StringifyNode[], item, i) => {
let comment: string | null = null
if (isNode(item)) {
if (!chompKeep && item.spaceBefore) nodes.push({ comment: true, str: '' })
if (item.commentBefore) {
// This match will always succeed on a non-empty string
for (const line of item.commentBefore.match(/^.*$/gm) as string[])
nodes.push({ comment: true, str: `#${line}` })
}
if (item.comment) {
comment = item.comment
singleLineOutput = false
}
} else if (isPair(item)) {
const ik = isNode(item.key) ? item.key : null
if (ik) {
if (!chompKeep && ik.spaceBefore) nodes.push({ comment: true, str: '' })
if (ik.commentBefore) {
// This match will always succeed on a non-empty string
for (const line of ik.commentBefore.match(/^.*$/gm) as string[])
nodes.push({ comment: true, str: `#${line}` })
}
if (ik.comment) singleLineOutput = false
}
if (inFlow) {
const iv = isNode(item.value) ? item.value : null
if (iv) {
if (iv.comment) comment = iv.comment
if (iv.comment || iv.commentBefore) singleLineOutput = false
} else if (item.value == null && ik && ik.comment) {
comment = ik.comment
}
}
}
chompKeep = false
let str = stringify(
item,
ctx,
() => (comment = null),
() => (chompKeep = true)
)
if (inFlow && i < items.length - 1) str += ','
str = addComment(str, itemIndent, comment)
if (chompKeep && (comment || inFlow)) chompKeep = false
nodes.push({ comment: false, str })
return nodes
}, [])
let str: string
if (nodes.length === 0) {
str = flowChars.start + flowChars.end
} else if (inFlow) {
const { start, end } = flowChars
const strings = nodes.map(n => n.str)
let singleLineLength = 2
for (const node of nodes) {
if (node.comment || node.str.includes('\n')) {
singleLineOutput = false
break
}
singleLineLength += node.str.length + 2
}
if (
!singleLineOutput ||
singleLineLength > Collection.maxFlowStringSingleLineLength
) {
str = start
for (const s of strings) {
str += s ? `\n${indentStep}${indent}${s}` : '\n'
}
str += `\n${indent}${end}`
} else {
str = `${start} ${strings.join(' ')} ${end}`
}
} else {
const strings = nodes.map(blockItem)
str = strings.shift() || ''
for (const s of strings) str += s ? `\n${indent}${s}` : '\n'
}
if (comment) {
str += '\n' + comment.replace(/^/gm, `${indent}#`)
if (onComment) onComment()
} else if (chompKeep && onChompKeep) onChompKeep()
return str
}