You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
8.2 KiB
200 lines
8.2 KiB
import { isPair } from '../nodes/Node.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) { |
|
const isMap = fc.start.source === '{'; |
|
const fcName = isMap ? 'flow map' : 'flow sequence'; |
|
const coll = isMap |
|
? new YAMLMap(ctx.schema) |
|
: new YAMLSeq(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 };
|
|
|