forked from Fediversity/Fediversity
200 lines
8.3 KiB
JavaScript
200 lines
8.3 KiB
JavaScript
import { isPair } from '../nodes/identity.js';
|
|
import { Pair } from '../nodes/Pair.js';
|
|
import { YAMLMap } from '../nodes/YAMLMap.js';
|
|
import { YAMLSeq } from '../nodes/YAMLSeq.js';
|
|
import { resolveEnd } from './resolve-end.js';
|
|
import { resolveProps } from './resolve-props.js';
|
|
import { containsNewline } from './util-contains-newline.js';
|
|
import { mapIncludes } from './util-map-includes.js';
|
|
|
|
const blockMsg = 'Block collections are not allowed within flow collections';
|
|
const isBlock = (token) => token && (token.type === 'block-map' || token.type === 'block-seq');
|
|
function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) {
|
|
const isMap = fc.start.source === '{';
|
|
const fcName = isMap ? 'flow map' : 'flow sequence';
|
|
const NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq));
|
|
const coll = new NodeClass(ctx.schema);
|
|
coll.flow = true;
|
|
const atRoot = ctx.atRoot;
|
|
if (atRoot)
|
|
ctx.atRoot = false;
|
|
let offset = fc.offset + fc.start.source.length;
|
|
for (let i = 0; i < fc.items.length; ++i) {
|
|
const collItem = fc.items[i];
|
|
const { start, key, sep, value } = collItem;
|
|
const props = resolveProps(start, {
|
|
flow: fcName,
|
|
indicator: 'explicit-key-ind',
|
|
next: key ?? sep?.[0],
|
|
offset,
|
|
onError,
|
|
startOnNewline: false
|
|
});
|
|
if (!props.found) {
|
|
if (!props.anchor && !props.tag && !sep && !value) {
|
|
if (i === 0 && props.comma)
|
|
onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`);
|
|
else if (i < fc.items.length - 1)
|
|
onError(props.start, 'UNEXPECTED_TOKEN', `Unexpected empty item in ${fcName}`);
|
|
if (props.comment) {
|
|
if (coll.comment)
|
|
coll.comment += '\n' + props.comment;
|
|
else
|
|
coll.comment = props.comment;
|
|
}
|
|
offset = props.end;
|
|
continue;
|
|
}
|
|
if (!isMap && ctx.options.strict && containsNewline(key))
|
|
onError(key, // checked by containsNewline()
|
|
'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line');
|
|
}
|
|
if (i === 0) {
|
|
if (props.comma)
|
|
onError(props.comma, 'UNEXPECTED_TOKEN', `Unexpected , in ${fcName}`);
|
|
}
|
|
else {
|
|
if (!props.comma)
|
|
onError(props.start, 'MISSING_CHAR', `Missing , between ${fcName} items`);
|
|
if (props.comment) {
|
|
let prevItemComment = '';
|
|
loop: for (const st of start) {
|
|
switch (st.type) {
|
|
case 'comma':
|
|
case 'space':
|
|
break;
|
|
case 'comment':
|
|
prevItemComment = st.source.substring(1);
|
|
break loop;
|
|
default:
|
|
break loop;
|
|
}
|
|
}
|
|
if (prevItemComment) {
|
|
let prev = coll.items[coll.items.length - 1];
|
|
if (isPair(prev))
|
|
prev = prev.value ?? prev.key;
|
|
if (prev.comment)
|
|
prev.comment += '\n' + prevItemComment;
|
|
else
|
|
prev.comment = prevItemComment;
|
|
props.comment = props.comment.substring(prevItemComment.length + 1);
|
|
}
|
|
}
|
|
}
|
|
if (!isMap && !sep && !props.found) {
|
|
// item is a value in a seq
|
|
// → key & sep are empty, start does not include ? or :
|
|
const valueNode = value
|
|
? composeNode(ctx, value, props, onError)
|
|
: composeEmptyNode(ctx, props.end, sep, null, props, onError);
|
|
coll.items.push(valueNode);
|
|
offset = valueNode.range[2];
|
|
if (isBlock(value))
|
|
onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg);
|
|
}
|
|
else {
|
|
// item is a key+value pair
|
|
// key value
|
|
const keyStart = props.end;
|
|
const keyNode = key
|
|
? composeNode(ctx, key, props, onError)
|
|
: composeEmptyNode(ctx, keyStart, start, null, props, onError);
|
|
if (isBlock(key))
|
|
onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg);
|
|
// value properties
|
|
const valueProps = resolveProps(sep ?? [], {
|
|
flow: fcName,
|
|
indicator: 'map-value-ind',
|
|
next: value,
|
|
offset: keyNode.range[2],
|
|
onError,
|
|
startOnNewline: false
|
|
});
|
|
if (valueProps.found) {
|
|
if (!isMap && !props.found && ctx.options.strict) {
|
|
if (sep)
|
|
for (const st of sep) {
|
|
if (st === valueProps.found)
|
|
break;
|
|
if (st.type === 'newline') {
|
|
onError(st, 'MULTILINE_IMPLICIT_KEY', 'Implicit keys of flow sequence pairs need to be on a single line');
|
|
break;
|
|
}
|
|
}
|
|
if (props.start < valueProps.found.offset - 1024)
|
|
onError(valueProps.found, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit flow sequence key');
|
|
}
|
|
}
|
|
else if (value) {
|
|
if ('source' in value && value.source && value.source[0] === ':')
|
|
onError(value, 'MISSING_CHAR', `Missing space after : in ${fcName}`);
|
|
else
|
|
onError(valueProps.start, 'MISSING_CHAR', `Missing , or : between ${fcName} items`);
|
|
}
|
|
// value value
|
|
const valueNode = value
|
|
? composeNode(ctx, value, valueProps, onError)
|
|
: valueProps.found
|
|
? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError)
|
|
: null;
|
|
if (valueNode) {
|
|
if (isBlock(value))
|
|
onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg);
|
|
}
|
|
else if (valueProps.comment) {
|
|
if (keyNode.comment)
|
|
keyNode.comment += '\n' + valueProps.comment;
|
|
else
|
|
keyNode.comment = valueProps.comment;
|
|
}
|
|
const pair = new Pair(keyNode, valueNode);
|
|
if (ctx.options.keepSourceTokens)
|
|
pair.srcToken = collItem;
|
|
if (isMap) {
|
|
const map = coll;
|
|
if (mapIncludes(ctx, map.items, keyNode))
|
|
onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique');
|
|
map.items.push(pair);
|
|
}
|
|
else {
|
|
const map = new YAMLMap(ctx.schema);
|
|
map.flow = true;
|
|
map.items.push(pair);
|
|
coll.items.push(map);
|
|
}
|
|
offset = valueNode ? valueNode.range[2] : valueProps.end;
|
|
}
|
|
}
|
|
const expectedEnd = isMap ? '}' : ']';
|
|
const [ce, ...ee] = fc.end;
|
|
let cePos = offset;
|
|
if (ce && ce.source === expectedEnd)
|
|
cePos = ce.offset + ce.source.length;
|
|
else {
|
|
const name = fcName[0].toUpperCase() + fcName.substring(1);
|
|
const msg = atRoot
|
|
? `${name} must end with a ${expectedEnd}`
|
|
: `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`;
|
|
onError(offset, atRoot ? 'MISSING_CHAR' : 'BAD_INDENT', msg);
|
|
if (ce && ce.source.length !== 1)
|
|
ee.unshift(ce);
|
|
}
|
|
if (ee.length > 0) {
|
|
const end = resolveEnd(ee, cePos, ctx.options.strict, onError);
|
|
if (end.comment) {
|
|
if (coll.comment)
|
|
coll.comment += '\n' + end.comment;
|
|
else
|
|
coll.comment = end.comment;
|
|
}
|
|
coll.range = [fc.offset, cePos, end.offset];
|
|
}
|
|
else {
|
|
coll.range = [fc.offset, cePos, cePos];
|
|
}
|
|
return coll;
|
|
}
|
|
|
|
export { resolveFlowCollection };
|