UNPKG

8.95 kBJavaScriptView Raw
1'use strict';
2
3var identity = require('../nodes/identity.js');
4var Pair = require('../nodes/Pair.js');
5var YAMLMap = require('../nodes/YAMLMap.js');
6var YAMLSeq = require('../nodes/YAMLSeq.js');
7var resolveEnd = require('./resolve-end.js');
8var resolveProps = require('./resolve-props.js');
9var utilContainsNewline = require('./util-contains-newline.js');
10var utilMapIncludes = require('./util-map-includes.js');
11
12const blockMsg = 'Block collections are not allowed within flow collections';
13const isBlock = (token) => token && (token.type === 'block-map' || token.type === 'block-seq');
14function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) {
15 const isMap = fc.start.source === '{';
16 const fcName = isMap ? 'flow map' : 'flow sequence';
17 const NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap.YAMLMap : YAMLSeq.YAMLSeq));
18 const coll = new NodeClass(ctx.schema);
19 coll.flow = true;
20 const atRoot = ctx.atRoot;
21 if (atRoot)
22 ctx.atRoot = false;
23 if (ctx.atKey)
24 ctx.atKey = false;
25 let offset = fc.offset + fc.start.source.length;
26 for (let i = 0; i < fc.items.length; ++i) {
27 const collItem = fc.items[i];
28 const { start, key, sep, value } = collItem;
29 const props = resolveProps.resolveProps(start, {
30 flow: fcName,
31 indicator: 'explicit-key-ind',
32 next: key ?? sep?.[0],
33 offset,
34 onError,
35 parentIndent: fc.indent,
36 startOnNewline: false
37 });
38 if (!props.found) {
39 if (!props.anchor && !props.tag && !sep && !value) {
40 if (i === 0 && props.comma)
41 onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`);
42 else if (i < fc.items.length - 1)
43 onError(props.start, 'UNEXPECTED_TOKEN', `Unexpected empty item in ${fcName}`);
44 if (props.comment) {
45 if (coll.comment)
46 coll.comment += '\n' + props.comment;
47 else
48 coll.comment = props.comment;
49 }
50 offset = props.end;
51 continue;
52 }
53 if (!isMap && ctx.options.strict && utilContainsNewline.containsNewline(key))
54 onError(key, // checked by containsNewline()
55 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line');
56 }
57 if (i === 0) {
58 if (props.comma)
59 onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`);
60 }
61 else {
62 if (!props.comma)
63 onError(props.start, 'MISSING_CHAR', `Missing , between ${fcName} items`);
64 if (props.comment) {
65 let prevItemComment = '';
66 loop: for (const st of start) {
67 switch (st.type) {
68 case 'comma':
69 case 'space':
70 break;
71 case 'comment':
72 prevItemComment = st.source.substring(1);
73 break loop;
74 default:
75 break loop;
76 }
77 }
78 if (prevItemComment) {
79 let prev = coll.items[coll.items.length - 1];
80 if (identity.isPair(prev))
81 prev = prev.value ?? prev.key;
82 if (prev.comment)
83 prev.comment += '\n' + prevItemComment;
84 else
85 prev.comment = prevItemComment;
86 props.comment = props.comment.substring(prevItemComment.length + 1);
87 }
88 }
89 }
90 if (!isMap && !sep && !props.found) {
91 // item is a value in a seq
92 // → key & sep are empty, start does not include ? or :
93 const valueNode = value
94 ? composeNode(ctx, value, props, onError)
95 : composeEmptyNode(ctx, props.end, sep, null, props, onError);
96 coll.items.push(valueNode);
97 offset = valueNode.range[2];
98 if (isBlock(value))
99 onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg);
100 }
101 else {
102 // item is a key+value pair
103 // key value
104 ctx.atKey = true;
105 const keyStart = props.end;
106 const keyNode = key
107 ? composeNode(ctx, key, props, onError)
108 : composeEmptyNode(ctx, keyStart, start, null, props, onError);
109 if (isBlock(key))
110 onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg);
111 ctx.atKey = false;
112 // value properties
113 const valueProps = resolveProps.resolveProps(sep ?? [], {
114 flow: fcName,
115 indicator: 'map-value-ind',
116 next: value,
117 offset: keyNode.range[2],
118 onError,
119 parentIndent: fc.indent,
120 startOnNewline: false
121 });
122 if (valueProps.found) {
123 if (!isMap && !props.found && ctx.options.strict) {
124 if (sep)
125 for (const st of sep) {
126 if (st === valueProps.found)
127 break;
128 if (st.type === 'newline') {
129 onError(st, 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line');
130 break;
131 }
132 }
133 if (props.start < valueProps.found.offset - 1024)
134 onError(valueProps.found, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit flow sequence key');
135 }
136 }
137 else if (value) {
138 if ('source' in value && value.source && value.source[0] === ':')
139 onError(value, 'MISSING_CHAR', `Missing space after : in ${fcName}`);
140 else
141 onError(valueProps.start, 'MISSING_CHAR', `Missing , or : between ${fcName} items`);
142 }
143 // value value
144 const valueNode = value
145 ? composeNode(ctx, value, valueProps, onError)
146 : valueProps.found
147 ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError)
148 : null;
149 if (valueNode) {
150 if (isBlock(value))
151 onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg);
152 }
153 else if (valueProps.comment) {
154 if (keyNode.comment)
155 keyNode.comment += '\n' + valueProps.comment;
156 else
157 keyNode.comment = valueProps.comment;
158 }
159 const pair = new Pair.Pair(keyNode, valueNode);
160 if (ctx.options.keepSourceTokens)
161 pair.srcToken = collItem;
162 if (isMap) {
163 const map = coll;
164 if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
165 onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique');
166 map.items.push(pair);
167 }
168 else {
169 const map = new YAMLMap.YAMLMap(ctx.schema);
170 map.flow = true;
171 map.items.push(pair);
172 const endRange = (valueNode ?? keyNode).range;
173 map.range = [keyNode.range[0], endRange[1], endRange[2]];
174 coll.items.push(map);
175 }
176 offset = valueNode ? valueNode.range[2] : valueProps.end;
177 }
178 }
179 const expectedEnd = isMap ? '}' : ']';
180 const [ce, ...ee] = fc.end;
181 let cePos = offset;
182 if (ce && ce.source === expectedEnd)
183 cePos = ce.offset + ce.source.length;
184 else {
185 const name = fcName[0].toUpperCase() + fcName.substring(1);
186 const msg = atRoot
187 ? `${name} must end with a ${expectedEnd}`
188 : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`;
189 onError(offset, atRoot ? 'MISSING_CHAR' : 'BAD_INDENT', msg);
190 if (ce && ce.source.length !== 1)
191 ee.unshift(ce);
192 }
193 if (ee.length > 0) {
194 const end = resolveEnd.resolveEnd(ee, cePos, ctx.options.strict, onError);
195 if (end.comment) {
196 if (coll.comment)
197 coll.comment += '\n' + end.comment;
198 else
199 coll.comment = end.comment;
200 }
201 coll.range = [fc.offset, cePos, end.offset];
202 }
203 else {
204 coll.range = [fc.offset, cePos, cePos];
205 }
206 return coll;
207}
208
209exports.resolveFlowCollection = resolveFlowCollection;