function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) { let spaceBefore = false; let atNewline = startOnNewline; let hasSpace = startOnNewline; let comment = ''; let commentSep = ''; let hasNewline = false; let reqSpace = false; let tab = null; let anchor = null; let tag = null; let newlineAfterProp = null; let comma = null; let found = null; let start = null; for (const token of tokens) { if (reqSpace) { if (token.type !== 'space' && token.type !== 'newline' && token.type !== 'comma') onError(token.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); reqSpace = false; } if (tab) { if (atNewline && token.type !== 'comment' && token.type !== 'newline') { onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); } tab = null; } switch (token.type) { case 'space': // At the doc level, tabs at line start may be parsed // as leading white space rather than indentation. // In a flow collection, only the parser handles indent. if (!flow && (indicator !== 'doc-start' || next?.type !== 'flow-collection') && token.source.includes('\t')) { tab = token; } hasSpace = true; break; case 'comment': { if (!hasSpace) onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters'); const cb = token.source.substring(1) || ' '; if (!comment) comment = cb; else comment += commentSep + cb; commentSep = ''; atNewline = false; break; } case 'newline': if (atNewline) { if (comment) comment += token.source; else spaceBefore = true; } else commentSep += token.source; atNewline = true; hasNewline = true; if (anchor || tag) newlineAfterProp = token; hasSpace = true; break; case 'anchor': if (anchor) onError(token, 'MULTIPLE_ANCHORS', 'A node can have at most one anchor'); if (token.source.endsWith(':')) onError(token.offset + token.source.length - 1, 'BAD_ALIAS', 'Anchor ending in : is ambiguous', true); anchor = token; if (start === null) start = token.offset; atNewline = false; hasSpace = false; reqSpace = true; break; case 'tag': { if (tag) onError(token, 'MULTIPLE_TAGS', 'A node can have at most one tag'); tag = token; if (start === null) start = token.offset; atNewline = false; hasSpace = false; reqSpace = true; break; } case indicator: // Could here handle preceding comments differently if (anchor || tag) onError(token, 'BAD_PROP_ORDER', `Anchors and tags must be after the ${token.source} indicator`); if (found) onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.source} in ${flow ?? 'collection'}`); found = token; atNewline = indicator === 'seq-item-ind' || indicator === 'explicit-key-ind'; hasSpace = false; break; case 'comma': if (flow) { if (comma) onError(token, 'UNEXPECTED_TOKEN', `Unexpected , in ${flow}`); comma = token; atNewline = false; hasSpace = false; break; } // else fallthrough default: onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.type} token`); atNewline = false; hasSpace = false; } } const last = tokens[tokens.length - 1]; const end = last ? last.offset + last.source.length : offset; if (reqSpace && next && next.type !== 'space' && next.type !== 'newline' && next.type !== 'comma' && (next.type !== 'scalar' || next.source !== '')) { onError(next.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); } if (tab && ((atNewline && tab.indent <= parentIndent) || next?.type === 'block-map' || next?.type === 'block-seq')) onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); return { comma, found, spaceBefore, comment, hasNewline, anchor, tag, newlineAfterProp, end, start: start ?? end }; } export { resolveProps };