diff --git a/src/interchange/yaml.zig b/src/interchange/yaml.zig index b76a0af3a8..28f7de06fa 100644 --- a/src/interchange/yaml.zig +++ b/src/interchange/yaml.zig @@ -278,6 +278,8 @@ pub fn Parser(comptime enc: Encoding) type { context: Context.Stack, block_indents: Indent.Stack, + explicit_document_start_line: ?Line, + // anchors: Anchors, anchors: bun.StringHashMap(Expr), // aliases: PendingAliases, @@ -299,13 +301,12 @@ pub fn Parser(comptime enc: Encoding) type { stack_check: bun.StackCheck, - const Whitespace = struct { - pos: Pos, - unit: enc.unit(), - - pub const space: Whitespace = .{ .unit = ' ', .pos = .zero }; - pub const tab: Whitespace = .{ .unit = '\t', .pos = .zero }; - pub const newline: Whitespace = .{ .unit = '\n', .pos = .zero }; + const Whitespace = union(enum) { + source: struct { + pos: Pos, + unit: enc.unit(), + }, + new: enc.unit(), }; pub fn init(allocator: std.mem.Allocator, input: []const enc.unit()) @This() { @@ -320,6 +321,7 @@ pub fn Parser(comptime enc: Encoding) type { // .literal = null, .context = .init(allocator), .block_indents = .init(allocator), + .explicit_document_start_line = null, // .anchors = .{ .map = .init(allocator) }, .anchors = .init(allocator), // .aliases = .{ .list = .init(allocator) }, @@ -407,10 +409,10 @@ pub fn Parser(comptime enc: Encoding) type { try log.addError(source, e.pos.loc(), "Unexpected EOF"); }, .unexpected_token => |e| { - try log.addError(source, e.pos.loc(), "Expected token"); + try log.addError(source, e.pos.loc(), "Unexpected token"); }, .unexpected_character => |e| { - try log.addError(source, e.pos.loc(), "Expected character"); + try log.addError(source, e.pos.loc(), "Unexpected character"); }, .invalid_directive => |e| { try log.addError(source, e.pos.loc(), "Invalid directive"); @@ -486,6 +488,10 @@ pub fn Parser(comptime enc: Encoding) type { } }; + fn unexpectedToken() error{UnexpectedToken} { + return error.UnexpectedToken; + } + pub fn parse(self: *@This()) ParseError!Stream { try self.scan(.{ .first_scan = true }); @@ -693,31 +699,39 @@ pub fn Parser(comptime enc: Encoding) type { try self.scan(.{}); } + self.explicit_document_start_line = null; + if (self.token.data == .document_start) { + self.explicit_document_start_line = self.token.line; try self.scan(.{}); } else if (directives.items.len > 0) { // if there's directives they must end with '---' - return error.UnexpectedToken; + return unexpectedToken(); } const root = try self.parseNode(.{}); - // If document_start or document_end follows, consume it + // If document_start it needs to create a new document. + // If document_end, consume as many as possible. They should + // not create new documents. switch (self.token.data) { .eof => {}, - .document_start => { - try self.scan(.{}); - }, + .document_start => {}, .document_end => { const document_end_line = self.token.line; try self.scan(.{}); + // consume all bare documents + while (self.token.data == .document_end) { + try self.scan(.{}); + } + if (self.token.line == document_end_line) { - return error.UnexpectedToken; + return unexpectedToken(); } }, else => { - return error.UnexpectedToken; + return unexpectedToken(); }, } @@ -747,7 +761,7 @@ pub fn Parser(comptime enc: Encoding) type { } if (self.token.data != .collect_entry) { - return error.UnexpectedToken; + return unexpectedToken(); } try self.scan(.{}); @@ -803,7 +817,7 @@ pub fn Parser(comptime enc: Encoding) type { }, .mapping_value => {}, else => { - return error.UnexpectedToken; + return unexpectedToken(); }, } @@ -874,9 +888,6 @@ pub fn Parser(comptime enc: Encoding) type { const sequence_indent = self.token.indent; // const sequence_line = self.token.line; - // try self.context.set(.block_in); - // defer self.context.unset(.block_in); - try self.block_indents.push(sequence_indent); defer self.block_indents.pop(); @@ -934,6 +945,45 @@ pub fn Parser(comptime enc: Encoding) type { break :item try self.parseNode(.{}); }, + .tag, + .anchor, + => item: { + // consume anchor and/or tag, then decide if the next node + // should be parsed. + var has_tag: ?Token(enc) = null; + var has_anchor: ?Token(enc) = null; + + next: switch (self.token.data) { + .tag => { + if (has_tag != null) { + return unexpectedToken(); + } + has_tag = self.token; + + try self.scan(.{ .additional_parent_indent = entry_indent.add(1), .tag = self.token.data.tag }); + continue :next self.token.data; + }, + .anchor => |anchor| { + _ = anchor; + if (has_anchor != null) { + return unexpectedToken(); + } + has_anchor = self.token; + + const tag = if (has_tag) |tag| tag.data.tag else .none; + try self.scan(.{ .additional_parent_indent = entry_indent.add(1), .tag = tag }); + continue :next self.token.data; + }, + .sequence_entry => { + if (self.token.indent.isLessThanOrEqual(sequence_indent)) { + const tag = if (has_tag) |tag| tag.data.tag else .none; + break :item tag.resolveNull(entry_start.add(2).loc()); + } + break :item try self.parseNode(.{ .scanned_tag = has_tag, .scanned_anchor = has_anchor }); + }, + else => break :item try self.parseNode(.{ .scanned_tag = has_tag, .scanned_anchor = has_anchor }), + } + }, else => try self.parseNode(.{}), }; @@ -951,6 +1001,16 @@ pub fn Parser(comptime enc: Encoding) type { mapping_indent: Indent, mapping_line: Line, ) ParseError!Expr { + if (self.explicit_document_start_line) |explicit_document_start_line| { + if (mapping_line == explicit_document_start_line) { + // TODO: more specific error + return error.UnexpectedToken; + } + } + + try self.block_indents.push(mapping_indent); + defer self.block_indents.pop(); + var props: std.ArrayList(G.Property) = .init(self.allocator); { @@ -958,32 +1018,41 @@ pub fn Parser(comptime enc: Encoding) type { // defer self.context.unset(.block_in); // get the first value - try self.block_indents.push(mapping_indent); - defer self.block_indents.pop(); const mapping_value_start = self.token.start; const mapping_value_line = self.token.line; - try self.scan(.{}); - const value: Expr = switch (self.token.data) { - .sequence_entry => value: { - if (self.token.line == mapping_value_line) { - return error.UnexpectedToken; + // it's a !!set entry + .mapping_key => value: { + if (self.token.line == mapping_line) { + return unexpectedToken(); } - - if (self.token.indent.isLessThan(mapping_indent)) { - break :value .init(E.Null, .{}, mapping_value_start.loc()); - } - - break :value try self.parseNode(.{ .current_mapping_indent = mapping_indent }); + break :value .init(E.Null, .{}, mapping_value_start.loc()); }, else => value: { - if (self.token.line != mapping_value_line and self.token.indent.isLessThanOrEqual(mapping_indent)) { - break :value .init(E.Null, .{}, mapping_value_start.loc()); - } + try self.scan(.{}); - break :value try self.parseNode(.{ .current_mapping_indent = mapping_indent }); + switch (self.token.data) { + .sequence_entry => { + if (self.token.line == mapping_value_line) { + return unexpectedToken(); + } + + if (self.token.indent.isLessThan(mapping_indent)) { + break :value .init(E.Null, .{}, mapping_value_start.loc()); + } + + break :value try self.parseNode(.{ .current_mapping_indent = mapping_indent }); + }, + else => { + if (self.token.line != mapping_value_line and self.token.indent.isLessThanOrEqual(mapping_indent)) { + break :value .init(E.Null, .{}, mapping_value_start.loc()); + } + + break :value try self.parseNode(.{ .current_mapping_indent = mapping_indent }); + }, + } }, }; @@ -1028,14 +1097,17 @@ pub fn Parser(comptime enc: Encoding) type { try self.context.set(.block_in); defer self.context.unset(.block_in); + var previous_line = mapping_line; + while (switch (self.token.data) { .eof, .document_start, .document_end, => false, else => true, - } and self.token.indent == mapping_indent and self.token.line != mapping_line) { + } and self.token.indent == mapping_indent and self.token.line != previous_line) { const key_line = self.token.line; + previous_line = key_line; const explicit_key = self.token.data == .mapping_key; const key = try self.parseNode(.{ .current_mapping_indent = mapping_indent }); @@ -1051,44 +1123,53 @@ pub fn Parser(comptime enc: Encoding) type { }); continue; } - return error.UnexpectedToken; + return unexpectedToken(); }, .mapping_value => { if (key_line != self.token.line) { return error.MultilineImplicitKey; } }, + .mapping_key => {}, else => { - return error.UnexpectedToken; + return unexpectedToken(); }, } - try self.block_indents.push(mapping_indent); - defer self.block_indents.pop(); - const mapping_value_line = self.token.line; const mapping_value_start = self.token.start; - try self.scan(.{}); - const value: Expr = switch (self.token.data) { - .sequence_entry => value: { + // it's a !!set entry + .mapping_key => value: { if (self.token.line == key_line) { - return error.UnexpectedToken; + return unexpectedToken(); } - - if (self.token.indent.isLessThan(mapping_indent)) { - break :value .init(E.Null, .{}, mapping_value_start.loc()); - } - - break :value try self.parseNode(.{ .current_mapping_indent = mapping_indent }); + break :value .init(E.Null, .{}, mapping_value_start.loc()); }, else => value: { - if (self.token.line != mapping_value_line and self.token.indent.isLessThanOrEqual(mapping_indent)) { - break :value .init(E.Null, .{}, mapping_value_start.loc()); - } + try self.scan(.{}); - break :value try self.parseNode(.{ .current_mapping_indent = mapping_indent }); + switch (self.token.data) { + .sequence_entry => { + if (self.token.line == key_line) { + return unexpectedToken(); + } + + if (self.token.indent.isLessThan(mapping_indent)) { + break :value .init(E.Null, .{}, mapping_value_start.loc()); + } + + break :value try self.parseNode(.{ .current_mapping_indent = mapping_indent }); + }, + else => { + if (self.token.line != mapping_value_line and self.token.indent.isLessThanOrEqual(mapping_indent)) { + break :value .init(E.Null, .{}, mapping_value_start.loc()); + } + + break :value try self.parseNode(.{ .current_mapping_indent = mapping_indent }); + }, + } }, }; @@ -1237,6 +1318,8 @@ pub fn Parser(comptime enc: Encoding) type { const ParseNodeOptions = struct { current_mapping_indent: ?Indent = null, explicit_mapping_key: bool = false, + scanned_tag: ?Token(enc) = null, + scanned_anchor: ?Token(enc) = null, }; fn parseNode(self: *@This(), opts: ParseNodeOptions) ParseError!Expr { @@ -1247,6 +1330,14 @@ pub fn Parser(comptime enc: Encoding) type { // c-ns-properties var node_props: NodeProperties = .{}; + if (opts.scanned_tag) |tag| { + try node_props.setTag(tag); + } + + if (opts.scanned_anchor) |anchor| { + try node_props.setAnchor(anchor); + } + const node: Expr = node: switch (self.token.data) { .eof, .document_start, @@ -1273,8 +1364,19 @@ pub fn Parser(comptime enc: Encoding) type { }, .alias => |alias| { - if (node_props.hasAnchorOrTag()) { - return error.UnexpectedToken; + const alias_start = self.token.start; + const alias_indent = self.token.indent; + const alias_line = self.token.line; + + if (node_props.has_anchor) |anchor| { + if (anchor.line == alias_line) { + return unexpectedToken(); + } + } + if (node_props.has_tag) |tag| { + if (tag.line == alias_line) { + return unexpectedToken(); + } } var copy = self.anchors.get(alias.slice(self.input)) orelse { @@ -1289,10 +1391,35 @@ pub fn Parser(comptime enc: Encoding) type { }; // update position from the anchor node to the alias node. - copy.loc = self.token.start.loc(); + copy.loc = alias_start.loc(); try self.scan(.{}); + if (self.token.data == .mapping_value) { + if (alias_line != self.token.line and !opts.explicit_mapping_key) { + return error.MultilineImplicitKey; + } + + if (self.context.get() == .flow_key) { + return copy; + } + + if (opts.current_mapping_indent) |current_mapping_indent| { + if (current_mapping_indent == alias_indent) { + return copy; + } + } + + const map = try self.parseBlockMapping( + copy, + alias_start, + alias_indent, + alias_line, + ); + + return map; + } + break :node copy; }, @@ -1346,17 +1473,17 @@ pub fn Parser(comptime enc: Encoding) type { if (node_props.hasAnchorOrTag()) { break :node .init(E.Null, .{}, self.pos.loc()); } - return error.UnexpectedToken; + return unexpectedToken(); }, .sequence_entry => { if (node_props.anchorLine()) |anchor_line| { if (anchor_line == self.token.line) { - return error.UnexpectedToken; + return unexpectedToken(); } } if (node_props.tagLine()) |tag_line| { if (tag_line == self.token.line) { - return error.UnexpectedToken; + return unexpectedToken(); } } @@ -1400,6 +1527,8 @@ pub fn Parser(comptime enc: Encoding) type { if (implicit_key_anchors.mapping_anchor) |mapping_anchor| { try self.anchors.put(mapping_anchor.slice(self.input), parent_map); } + + break :node parent_map; } break :node map; }, @@ -1411,7 +1540,7 @@ pub fn Parser(comptime enc: Encoding) type { // if (node_props.anchorLine()) |anchor_line| { // if (anchor_line == self.token.line) { - // return error.UnexpectedToken; + // return unexpectedToken(); // } // } @@ -1441,11 +1570,11 @@ pub fn Parser(comptime enc: Encoding) type { }, .mapping_value => { if (self.context.get() == .flow_key) { - return .init(E.Null, .{}, self.token.start.loc()); + break :node .init(E.Null, .{}, self.token.start.loc()); } if (opts.current_mapping_indent) |current_mapping_indent| { if (current_mapping_indent == self.token.indent) { - return .init(E.Null, .{}, self.token.start.loc()); + break :node .init(E.Null, .{}, self.token.start.loc()); } } const first_key: Expr = .init(E.Null, .{}, self.token.start.loc()); @@ -1461,7 +1590,7 @@ pub fn Parser(comptime enc: Encoding) type { const scalar_indent = self.token.indent; const scalar_line = self.token.line; - try self.scan(.{ .tag = node_props.tag() }); + try self.scan(.{ .tag = node_props.tag(), .outside_context = true }); if (self.token.data == .mapping_value) { // this might be the start of a new object with an implicit key @@ -1541,10 +1670,10 @@ pub fn Parser(comptime enc: Encoding) type { break :node scalar.data.toExpr(scalar_start, self.input); }, .directive => { - return error.UnexpectedToken; + return unexpectedToken(); }, .reserved => { - return error.UnexpectedToken; + return unexpectedToken(); }, }; @@ -1558,11 +1687,16 @@ pub fn Parser(comptime enc: Encoding) type { return error.MultipleTags; } + const resolved = switch (node.data) { + .e_null => node_props.tag().resolveNull(node.loc), + else => node, + }; + if (node_props.anchor()) |anchor| { - try self.anchors.put(anchor.slice(self.input), node); + try self.anchors.put(anchor.slice(self.input), resolved); } - return node; + return resolved; } fn next(self: *const @This()) enc.unit() { @@ -1688,11 +1822,36 @@ pub fn Parser(comptime enc: Encoding) type { try ctx.str_builder.appendSourceSlice(off, end); } + // may or may not contain whitespace + pub fn appendUnknownSourceSlice(ctx: *@This(), off: Pos, end: Pos) OOM!void { + for (off.cast()..end.cast()) |_pos| { + const pos: Pos = .from(_pos); + const unit = ctx.parser.input[pos.cast()]; + switch (unit) { + ' ', + '\t', + '\r', + '\n', + => { + try ctx.str_builder.appendSourceWhitespace(unit, pos); + }, + else => { + ctx.checkAppend(); + try ctx.str_builder.appendSource(unit, pos); + }, + } + } + } + pub fn append(ctx: *@This(), unit: enc.unit()) OOM!void { ctx.checkAppend(); try ctx.str_builder.append(unit); } + pub fn appendWhitespace(ctx: *@This(), unit: enc.unit()) OOM!void { + try ctx.str_builder.appendWhitespace(unit); + } + pub fn appendSlice(ctx: *@This(), str: []const enc.unit()) OOM!void { ctx.checkAppend(); try ctx.str_builder.appendSlice(str); @@ -1706,6 +1865,14 @@ pub fn Parser(comptime enc: Encoding) type { try ctx.str_builder.appendNTimes(unit, n); } + pub fn appendWhitespaceNTimes(ctx: *@This(), unit: enc.unit(), n: usize) OOM!void { + if (n == 0) { + return; + } + + try ctx.str_builder.appendWhitespaceNTimes(unit, n); + } + const Keywords = enum { null, Null, @@ -1798,7 +1965,7 @@ pub fn Parser(comptime enc: Encoding) type { pub fn tryResolveNumber( ctx: *@This(), parser: *Parser(enc), - first_char: enum { positive, negative, dot, none }, + first_char: enum { positive, negative, dot, other }, ) ResolveError!void { const nan = std.math.nan(f64); const inf = std.math.inf(f64); @@ -1913,7 +2080,7 @@ pub fn Parser(comptime enc: Encoding) type { } } }, - .none => {}, + .other => {}, } const start = parser.pos; @@ -1926,7 +2093,9 @@ pub fn Parser(comptime enc: Encoding) type { var @"-" = false; var hex = false; - parser.inc(1); + if (first_char != .negative and first_char != .positive) { + parser.inc(1); + } var first = true; @@ -1945,7 +2114,12 @@ pub fn Parser(comptime enc: Encoding) type { '\n', '\r', ':', - => break :end .{ parser.pos, true }, + => { + if (first and (first_char == .positive or first_char == .negative)) { + break :end .{ parser.pos, false }; + } + break :end .{ parser.pos, true }; + }, ',', ']', @@ -1993,6 +2167,7 @@ pub fn Parser(comptime enc: Encoding) type { 'e', 'E', => { + first = false; if (e) { hex = true; } @@ -2008,12 +2183,12 @@ pub fn Parser(comptime enc: Encoding) type { => |c| { hex = true; - defer first = false; if (first) { if (c == 'b' or c == 'B') { break :end .{ parser.pos, false }; } } + first = false; parser.inc(1); continue :end parser.next(); @@ -2076,7 +2251,7 @@ pub fn Parser(comptime enc: Encoding) type { }, }; - try ctx.appendSourceSlice(start, end); + try ctx.appendUnknownSourceSlice(start, end); if (!valid) { return; @@ -2165,7 +2340,7 @@ pub fn Parser(comptime enc: Encoding) type { }, else => { - try ctx.tryResolveNumber(self, .none); + try ctx.tryResolveNumber(self, .other); continue :next self.next(); }, } @@ -2181,13 +2356,41 @@ pub fn Parser(comptime enc: Encoding) type { return ctx.done(); } + switch (self.context.get()) { + .block_out, + .block_in, + .flow_in, + => {}, + .flow_key => { + switch (self.peek(1)) { + ',', + '[', + ']', + '{', + '}', + => { + return ctx.done(); + }, + else => {}, + } + }, + } + try ctx.appendSource(':', self.pos); self.inc(1); continue :next self.next(); }, '#' => { - if (self.pos == .zero or self.input[self.pos.sub(1).cast()] == ' ') { + const prev = self.input[self.pos.sub(1).cast()]; + if (self.pos == .zero or switch (prev) { + ' ', + '\t', + '\r', + '\n', + => true, + else => false, + }) { return ctx.done(); } @@ -2253,11 +2456,14 @@ pub fn Parser(comptime enc: Encoding) type { } } + // clear the leading whitespace before the newline. + ctx.parser.whitespace_buf.clearRetainingCapacity(); + if (lines == 0 and !self.isEof()) { - try ctx.append(' '); + try ctx.appendWhitespace(' '); } - try ctx.appendNTimes('\n', lines); + try ctx.appendWhitespaceNTimes('\n', lines); continue :next self.next(); }, @@ -2461,7 +2667,7 @@ pub fn Parser(comptime enc: Encoding) type { }, '0'...'9' => { - try ctx.tryResolveNumber(self, .none); + try ctx.tryResolveNumber(self, .other); continue :next self.next(); }, @@ -2479,7 +2685,7 @@ pub fn Parser(comptime enc: Encoding) type { }, else => { - try ctx.tryResolveNumber(self, .none); + try ctx.tryResolveNumber(self, .other); continue :next self.next(); }, } @@ -2507,6 +2713,12 @@ pub fn Parser(comptime enc: Encoding) type { var chomp: ?Chomp = null; next: switch (self.next()) { + 0 => { + return .{ + indent_indicator orelse .default, + chomp orelse .default, + }; + }, '1'...'9' => |digit| { if (indent_indicator != null) { return error.UnexpectedCharacter; @@ -2564,6 +2776,11 @@ pub fn Parser(comptime enc: Encoding) type { // the first newline is always excluded from a literal self.inc(1); + if (self.next() == '\t') { + // tab for indentation + return error.UnexpectedCharacter; + } + return .{ indent_indicator orelse .default, chomp orelse .default, @@ -2582,10 +2799,102 @@ pub fn Parser(comptime enc: Encoding) type { }; fn scanAutoIndentedLiteralScalar(self: *@This(), chomp: Chomp, folded: bool, start: Pos, line: Line) ScanLiteralScalarError!Token(enc) { - var leading_newlines: usize = 0; - var text: std.ArrayList(enc.unit()) = .init(self.allocator); + const LiteralScalarCtx = struct { + chomp: Chomp, + leading_newlines: usize, + text: std.ArrayList(enc.unit()), + start: Pos, + content_indent: Indent, + previous_indent: Indent, + max_leading_indent: Indent, + line: Line, + folded: bool, - const content_indent: Indent, const first = next: switch (self.next()) { + pub fn done(ctx: *@This(), was_eof: bool) OOM!Token(enc) { + switch (ctx.chomp) { + .keep => { + if (was_eof) { + try ctx.text.appendNTimes('\n', ctx.leading_newlines + 1); + } else if (ctx.text.items.len != 0) { + try ctx.text.appendNTimes('\n', ctx.leading_newlines); + } + }, + .clip => { + if (was_eof or ctx.text.items.len != 0) { + try ctx.text.append('\n'); + } + }, + .strip => { + // no trailing newlines + }, + } + + return .scalar(.{ + .start = ctx.start, + .indent = ctx.content_indent, + .line = ctx.line, + .resolved = .{ + .data = .{ .string = .{ .list = ctx.text } }, + .multiline = true, + }, + }); + } + + const AppendError = OOM || error{UnexpectedCharacter}; + + pub fn append(ctx: *@This(), c: enc.unit()) AppendError!void { + if (ctx.text.items.len == 0) { + if (ctx.content_indent.isLessThan(ctx.max_leading_indent)) { + return error.UnexpectedCharacter; + } + } + switch (ctx.folded) { + true => { + switch (ctx.leading_newlines) { + 0 => { + try ctx.text.append(c); + }, + 1 => { + if (ctx.previous_indent == ctx.content_indent) { + try ctx.text.appendSlice(&.{ ' ', c }); + } else { + try ctx.text.appendSlice(&.{ '\n', c }); + } + ctx.leading_newlines = 0; + }, + else => { + // leading_newlines because -1 for '\n\n' and +1 for c + try ctx.text.ensureUnusedCapacity(ctx.leading_newlines); + ctx.text.appendNTimesAssumeCapacity('\n', ctx.leading_newlines - 1); + ctx.text.appendAssumeCapacity(c); + ctx.leading_newlines = 0; + }, + } + }, + false => { + try ctx.text.ensureUnusedCapacity(ctx.leading_newlines + 1); + ctx.text.appendNTimesAssumeCapacity('\n', ctx.leading_newlines); + ctx.text.appendAssumeCapacity(c); + ctx.leading_newlines = 0; + }, + } + } + }; + + var ctx: LiteralScalarCtx = .{ + .chomp = chomp, + .text = .init(self.allocator), + .folded = folded, + .start = start, + .line = line, + + .leading_newlines = 0, + .content_indent = .none, + .previous_indent = .none, + .max_leading_indent = .none, + }; + + ctx.content_indent, const first = next: switch (self.next()) { 0 => { return .scalar(.{ .start = start, @@ -2607,7 +2916,11 @@ pub fn Parser(comptime enc: Encoding) type { '\n' => { self.newline(); self.inc(1); - leading_newlines += 1; + if (self.next() == '\t') { + // tab for indentation + return error.UnexpectedCharacter; + } + ctx.leading_newlines += 1; continue :next self.next(); }, @@ -2619,6 +2932,10 @@ pub fn Parser(comptime enc: Encoding) type { self.inc(1); } + if (ctx.max_leading_indent.isLessThan(indent)) { + ctx.max_leading_indent = indent; + } + self.line_indent = indent; continue :next self.next(); @@ -2629,30 +2946,11 @@ pub fn Parser(comptime enc: Encoding) type { }, }; - var previous_indent = content_indent; + ctx.previous_indent = ctx.content_indent; next: switch (first) { 0 => { - switch (chomp) { - .keep => { - try text.appendNTimes('\n', leading_newlines + 1); - }, - .clip => { - try text.append('\n'); - }, - .strip => { - // no trailing newlines - }, - } - return .scalar(.{ - .start = start, - .indent = content_indent, - .line = line, - .resolved = .{ - .data = .{ .string = .{ .list = text } }, - .multiline = true, - }, - }); + return ctx.done(true); }, '\r' => { @@ -2662,7 +2960,7 @@ pub fn Parser(comptime enc: Encoding) type { continue :next '\n'; }, '\n' => { - leading_newlines += 1; + ctx.leading_newlines += 1; self.newline(); self.inc(1); newlines: switch (self.next()) { @@ -2673,44 +2971,47 @@ pub fn Parser(comptime enc: Encoding) type { continue :newlines '\n'; }, '\n' => { - leading_newlines += 1; + ctx.leading_newlines += 1; self.newline(); self.inc(1); + if (self.next() == '\t') { + // tab for indentation + return error.UnexpectedCharacter; + } continue :newlines self.next(); }, ' ' => { - var indent: Indent = .from(1); - self.inc(1); + var indent: Indent = .from(0); while (self.next() == ' ') { indent.inc(1); - if (content_indent.isLessThan(indent)) { + if (ctx.content_indent.isLessThan(indent)) { switch (folded) { true => { - switch (leading_newlines) { + switch (ctx.leading_newlines) { 0 => { - try text.append(' '); + try ctx.text.append(' '); }, else => { - try text.ensureUnusedCapacity(leading_newlines + 1); - text.appendNTimesAssumeCapacity('\n', leading_newlines); - text.appendAssumeCapacity(' '); - leading_newlines = 0; + try ctx.text.ensureUnusedCapacity(ctx.leading_newlines + 1); + ctx.text.appendNTimesAssumeCapacity('\n', ctx.leading_newlines); + ctx.text.appendAssumeCapacity(' '); + ctx.leading_newlines = 0; }, } }, else => { - try text.ensureUnusedCapacity(leading_newlines + 1); - text.appendNTimesAssumeCapacity('\n', leading_newlines); - leading_newlines = 0; - text.appendAssumeCapacity(' '); + try ctx.text.ensureUnusedCapacity(ctx.leading_newlines + 1); + ctx.text.appendNTimesAssumeCapacity('\n', ctx.leading_newlines); + ctx.leading_newlines = 0; + ctx.text.appendAssumeCapacity(' '); }, } } self.inc(1); } - if (content_indent.isLessThan(indent)) { - previous_indent = self.line_indent; + if (ctx.content_indent.isLessThan(indent)) { + ctx.previous_indent = self.line_indent; } self.line_indent = indent; @@ -2720,91 +3021,54 @@ pub fn Parser(comptime enc: Encoding) type { } }, + '-' => { + if (self.line_indent == .none and self.remainStartsWith("---") and self.isAnyOrEofAt(" \t\n\r", 3)) { + return ctx.done(false); + } + + if (self.block_indents.get()) |block_indent| { + if (self.line_indent.isLessThanOrEqual(block_indent)) { + return ctx.done(false); + } + } else if (self.line_indent.isLessThan(ctx.content_indent)) { + return ctx.done(false); + } + + try ctx.append('-'); + + self.inc(1); + continue :next self.next(); + }, + + '.' => { + if (self.line_indent == .none and self.remainStartsWith("...") and self.isAnyOrEofAt(" \t\n\r", 3)) { + return ctx.done(false); + } + + if (self.block_indents.get()) |block_indent| { + if (self.line_indent.isLessThanOrEqual(block_indent)) { + return ctx.done(false); + } + } else if (self.line_indent.isLessThan(ctx.content_indent)) { + return ctx.done(false); + } + + try ctx.append('.'); + + self.inc(1); + continue :next self.next(); + }, + else => |c| { if (self.block_indents.get()) |block_indent| { if (self.line_indent.isLessThanOrEqual(block_indent)) { - switch (chomp) { - .keep => { - if (text.items.len != 0) { - try text.appendNTimes('\n', leading_newlines); - } - }, - .clip => { - if (text.items.len != 0) { - try text.append('\n'); - } - }, - .strip => { - // no trailing newlines - }, - } - return .scalar(.{ - .start = start, - .indent = content_indent, - .line = line, - .resolved = .{ - .data = .{ .string = .{ .list = text } }, - .multiline = true, - }, - }); - } else if (self.line_indent.isLessThan(content_indent)) { - switch (chomp) { - .keep => { - if (text.items.len != 0) { - try text.appendNTimes('\n', leading_newlines); - } - }, - .clip => { - if (text.items.len != 0) { - try text.append('\n'); - } - }, - .strip => { - // no trailing newlines - }, - } - return .scalar(.{ - .start = start, - .indent = content_indent, - .line = line, - .resolved = .{ - .data = .{ .string = .{ .list = text } }, - .multiline = true, - }, - }); + return ctx.done(false); } + } else if (self.line_indent.isLessThan(ctx.content_indent)) { + return ctx.done(false); } - switch (folded) { - true => { - switch (leading_newlines) { - 0 => { - try text.append(c); - }, - 1 => { - if (previous_indent == content_indent) { - try text.appendSlice(&.{ ' ', c }); - } else { - try text.appendSlice(&.{ '\n', c }); - } - leading_newlines = 0; - }, - else => { - // leading_newlines because -1 for '\n\n' and +1 for c - try text.ensureUnusedCapacity(leading_newlines); - text.appendNTimesAssumeCapacity('\n', leading_newlines - 1); - text.appendAssumeCapacity(c); - leading_newlines = 0; - }, - } - }, - false => { - try text.ensureUnusedCapacity(leading_newlines + 1); - text.appendNTimesAssumeCapacity('\n', leading_newlines); - text.appendAssumeCapacity(c); - leading_newlines = 0; - }, - } + try ctx.append(c); self.inc(1); continue :next self.next(); @@ -2948,26 +3212,22 @@ pub fn Parser(comptime enc: Encoding) type { const scalar_indent = self.line_indent; var text: std.ArrayList(enc.unit()) = .init(self.allocator); - var nl = false; - next: switch (self.next()) { 0 => return error.UnexpectedCharacter, '.' => { - if (nl and self.remainStartsWith("...") and self.isSWhiteOrBCharAt(3)) { + if (self.line_indent == .none and self.remainStartsWith("...") and self.isSWhiteOrBCharAt(3)) { return error.UnexpectedDocumentEnd; } - nl = false; try text.append('.'); self.inc(1); continue :next self.next(); }, '-' => { - if (nl and self.remainStartsWith("---") and self.isSWhiteOrBCharAt(3)) { + if (self.line_indent == .none and self.remainStartsWith("---") and self.isSWhiteOrBCharAt(3)) { return error.UnexpectedDocumentStart; } - nl = false; try text.append('-'); self.inc(1); continue :next self.next(); @@ -2988,14 +3248,12 @@ pub fn Parser(comptime enc: Encoding) type { return error.UnexpectedCharacter; } } - nl = true; continue :next self.next(); }, ' ', '\t', => { - nl = false; const off = self.pos; self.inc(1); self.skipSWhite(); @@ -3006,7 +3264,6 @@ pub fn Parser(comptime enc: Encoding) type { }, '"' => { - nl = false; self.inc(1); return .scalar(.{ .start = start, @@ -3023,7 +3280,6 @@ pub fn Parser(comptime enc: Encoding) type { }, '\\' => { - nl = false; self.inc(1); switch (self.next()) { '\r', @@ -3094,7 +3350,6 @@ pub fn Parser(comptime enc: Encoding) type { }, else => |c| { - nl = false; try text.append(c); self.inc(1); continue :next self.next(); @@ -3246,6 +3501,38 @@ pub fn Parser(comptime enc: Encoding) type { self.inc(1); var range = self.stringRange(); try self.trySkipNsTagChars(); + + // s-separate + switch (self.next()) { + 0, + ' ', + '\t', + '\r', + '\n', + => {}, + + ',', + '[', + ']', + '{', + '}', + => { + switch (self.context.get()) { + .block_out, + .block_in, + => { + return error.UnexpectedCharacter; + }, + .flow_in, + .flow_key, + => {}, + } + }, + else => { + return error.UnexpectedCharacter; + }, + } + const shorthand = range.end(); const tag: NodeTag = tag: { @@ -3367,6 +3654,8 @@ pub fn Parser(comptime enc: Encoding) type { /// (or in compact collections). First scan needs to /// count indentation. first_scan: bool = false, + + outside_context: bool = false, }; fn scan(self: *@This(), opts: ScanOptions) ScanError!void { @@ -3477,7 +3766,7 @@ pub fn Parser(comptime enc: Encoding) type { .flow_key, => { self.token.start = start; - return error.UnexpectedToken; + return unexpectedToken(); }, } @@ -3507,7 +3796,7 @@ pub fn Parser(comptime enc: Encoding) type { .line = self.line, }); - return error.UnexpectedToken; + return unexpectedToken(); }, .block_in, .block_out, @@ -3641,10 +3930,12 @@ pub fn Parser(comptime enc: Encoding) type { switch (self.context.get()) { .block_in, .block_out, + .flow_in, => { // scanPlainScalar }, - .flow_in, .flow_key => { + .flow_key, + => { self.inc(1); break :next .mappingValue(.{ .start = start, @@ -3653,7 +3944,6 @@ pub fn Parser(comptime enc: Encoding) type { }); }, } - // scanPlainScalar }, } @@ -3861,7 +4151,7 @@ pub fn Parser(comptime enc: Encoding) type { => {}, } self.token.start = start; - return error.UnexpectedToken; + return unexpectedToken(); }, '>' => { const start = self.pos; @@ -3878,7 +4168,7 @@ pub fn Parser(comptime enc: Encoding) type { => {}, } self.token.start = start; - return error.UnexpectedToken; + return unexpectedToken(); }, '\'' => { self.inc(1); @@ -3907,7 +4197,7 @@ pub fn Parser(comptime enc: Encoding) type { .indent = self.line_indent, .line = self.line, }); - return error.UnexpectedToken; + return unexpectedToken(); }, inline '\r', @@ -3929,8 +4219,8 @@ pub fn Parser(comptime enc: Encoding) type { .flow_key, => { if (self.block_indents.get()) |block_indent| { - if (self.token.line != previous_token_line and self.token.indent.isLessThan(block_indent)) { - return error.UnexpectedToken; + if (!opts.outside_context and self.token.line != previous_token_line and self.token.indent.isLessThanOrEqual(block_indent)) { + return unexpectedToken(); } } }, @@ -4025,9 +4315,17 @@ pub fn Parser(comptime enc: Encoding) type { /// /// positions `pos` on the next newline, or eof. Errors fn trySkipToNewLine(self: *@This()) error{UnexpectedCharacter}!void { - self.skipSWhite(); + var whitespace = false; + + if (self.isSWhite()) { + whitespace = true; + self.skipSWhite(); + } if (self.isChar('#')) { + if (!whitespace) { + return error.UnexpectedCharacter; + } self.inc(1); while (!self.isChar('\n') and !self.isChar('\r')) { self.inc(1); @@ -4285,34 +4583,60 @@ pub fn Parser(comptime enc: Encoding) type { } fn drainWhitespace(self: *@This()) OOM!void { - for (self.parser.whitespace_buf.items) |ws| { - if (comptime Environment.ci_assert) { - const actual = self.parser.input[ws.pos.cast()]; - bun.assert(actual == ws.unit); - } + const parser = self.parser; + defer parser.whitespace_buf.clearRetainingCapacity(); - switch (self.str) { - .range => |*range| { - if (range.isEmpty()) { - range.off = ws.pos; - range.end = ws.pos; + for (parser.whitespace_buf.items) |ws| { + switch (ws) { + .source => |source| { + if (comptime Environment.ci_assert) { + const actual = self.parser.input[source.pos.cast()]; + bun.assert(actual == source.unit); } - bun.assert(range.end == ws.pos); + switch (self.str) { + .range => |*range| { + if (range.isEmpty()) { + range.off = source.pos; + range.end = source.pos; + } - range.end = ws.pos.add(1); + bun.assert(range.end == source.pos); + + range.end = source.pos.add(1); + }, + .list => |*list| { + try list.append(source.unit); + }, + } }, - .list => |*list| { - try list.append(ws.unit); + .new => |unit| { + switch (self.str) { + .range => |range| { + var list: std.ArrayList(enc.unit()) = try .initCapacity(parser.allocator, range.len() + 1); + list.appendSliceAssumeCapacity(range.slice(parser.input)); + list.appendAssumeCapacity(unit); + self.str = .{ .list = list }; + }, + .list => |*list| { + try list.append(unit); + }, + } }, } } - - self.parser.whitespace_buf.clearRetainingCapacity(); } pub fn appendSourceWhitespace(self: *@This(), unit: enc.unit(), pos: Pos) OOM!void { - try self.parser.whitespace_buf.append(.{ .unit = unit, .pos = pos }); + try self.parser.whitespace_buf.append(.{ .source = .{ .unit = unit, .pos = pos } }); + } + + pub fn appendWhitespace(self: *@This(), unit: enc.unit()) OOM!void { + try self.parser.whitespace_buf.append(.{ .new = unit }); + } + + pub fn appendWhitespaceNTimes(self: *@This(), unit: enc.unit(), n: usize) OOM!void { + try self.parser.whitespace_buf.appendNTimes(.{ .new = unit }, n); } pub fn appendSourceSlice(self: *@This(), off: Pos, end: Pos) OOM!void { @@ -4484,6 +4808,24 @@ pub fn Parser(comptime enc: Encoding) type { /// '!!unknown' unknown: String.Range, + + pub fn resolveNull(this: NodeTag, loc: logger.Loc) Expr { + return switch (this) { + .none, + .bool, + .int, + .float, + .null, + .verbatim, + .unknown, + => .init(E.Null, .{}, loc), + + // non-specific tags become seq, map, or str + .non_specific, + .str, + => .init(E.String, .{}, loc), + }; + } }; pub const NodeScalar = union(enum) { diff --git a/test/js/bun/import-attributes/import-attributes.test.ts b/test/js/bun/import-attributes/import-attributes.test.ts index da0e55d668..6931edcf55 100644 --- a/test/js/bun/import-attributes/import-attributes.test.ts +++ b/test/js/bun/import-attributes/import-attributes.test.ts @@ -313,7 +313,7 @@ test("jsonc", async () => { }, "yaml": { "default": { - "// my json ": null, + "// my json": null, "key": "šŸ‘©ā€šŸ‘§ā€šŸ‘§value", }, "key": "šŸ‘©ā€šŸ‘§ā€šŸ‘§value", diff --git a/test/js/bun/yaml/__snapshots__/yaml.test.ts.snap b/test/js/bun/yaml/__snapshots__/yaml.test.ts.snap new file mode 100644 index 0000000000..d09ae64a30 --- /dev/null +++ b/test/js/bun/yaml/__snapshots__/yaml.test.ts.snap @@ -0,0 +1,171 @@ +// Bun Snapshot v1, https://bun.sh/docs/test/snapshots + +exports[`Bun.YAML parse issue 22286 2`] = ` +{ + "A Hat in Time": { + "ActPlando": { + "Dead Bird Studio Basement": "The Big Parade", + }, + "ActRandomizer": "insanity", + "BabyTrapWeight": 0, + "BadgeSellerMaxItems": 8, + "BadgeSellerMinItems": 5, + "BaseballBat": true, + "CTRLogic": { + "nothing": 0, + "scooter": 1, + "sprint": 0, + "time_stop_only": 0, + }, + "ChapterCostIncrement": 5, + "ChapterCostMinDifference": 5, + "CompassBadgeMode": "closest", + "DWAutoCompleteBonuses": true, + "DWEnableBonus": false, + "DWExcludeAnnoyingBonuses": true, + "DWExcludeAnnoyingContracts": true, + "DWExcludeCandles": true, + "DWShuffle": false, + "DWShuffleCountMax": 25, + "DWShuffleCountMin": 18, + "DWTimePieceRequirement": 15, + "DeathWishOnly": false, + "EnableDLC1": false, + "EnableDLC2": true, + "EnableDeathWish": false, + "EndGoal": { + "finale": 1, + "rush_hour": 0, + "seal_the_deal": 0, + }, + "ExcludeTour": false, + "FinalChapterMaxCost": 35, + "FinalChapterMinCost": 30, + "FinaleShuffle": false, + "HatItems": true, + "HighestChapterCost": 25, + "LaserTrapWeight": 0, + "LogicDifficulty": "moderate", + "LowestChapterCost": 5, + "MaxExtraTimePieces": "random-range-high-5-8", + "MaxPonCost": 80, + "MetroMaxPonCost": 50, + "MetroMinPonCost": 10, + "MinExtraYarn": "random-range-middle-5-15", + "MinPonCost": 20, + "NoPaintingSkips": true, + "NoTicketSkips": "rush_hour", + "NyakuzaThugMaxShopItems": 4, + "NyakuzaThugMinShopItems": 1, + "ParadeTrapWeight": 0, + "RandomizeHatOrder": "time_stop_last", + "ShipShapeCustomTaskGoal": 0, + "ShuffleActContracts": true, + "ShuffleAlpineZiplines": true, + "ShuffleStorybookPages": true, + "ShuffleSubconPaintings": true, + "StartWithCompassBadge": true, + "StartingChapter": { + "1": 1, + "2": 1, + "3": 1, + }, + "Tasksanity": false, + "TasksanityCheckCount": 18, + "TasksanityTaskStep": 1, + "TimePieceBalancePercent": "random-range-low-20-35", + "TrapChance": 0, + "UmbrellaLogic": true, + "YarnAvailable": "random-range-middle-40-50", + "YarnBalancePercent": 25, + "YarnCostMax": 8, + "YarnCostMin": 5, + "accessibility": { + "full": 1, + "minimal": 0, + }, + "death_link": false, + "exclude_locations": [ + "Queen Vanessa's Manor - Bedroom Chest", + "Queen Vanessa's Manor - Hall Chest", + "Act Completion (The Big Parade)", + ], + "non_local_items": [ + "Hookshot Badge", + "Umbrella", + "Dweller Mask", + ], + "priority_locations": [ + "Act Completion (Award Ceremony)", + "Badge Seller - Item 1", + "Badge Seller - Item 2", + "Mafia Boss Shop Item", + "Bluefin Tunnel Thug - Item 1", + "Green Clean Station Thug A - Item 1", + "Green Clean Station Thug B - Item 1", + "Main Station Thug A - Item 1", + "Main Station Thug B - Item 1", + "Main Station Thug C - Item 1", + "Pink Paw Station Thug - Item 1", + "Yellow Overpass Thug A - Item 1", + "Yellow Overpass Thug B - Item 1", + "Yellow Overpass Thug C - Item 1", + ], + "progression_balancing": "random-range-middle-40-50", + "start_inventory_from_pool": { + "Sprint Hat": 1, + }, + }, + "game": "A Hat in Time", + "name": "niyrme-AHiT{NUMBER}", + "requires": { + "version": "0.6.2", + }, + "x-options-async": { + "A Hat in Time": { + "+non_local_items": [ + "Brewing Hat", + "Ice Hat", + ], + "ChapterCostIncrement": 7, + "ChapterCostMinDifference": 7, + "EndGoal": { + "finale": 9, + "rush_hour": 1, + "seal_the_deal": 0, + }, + "FinalChapterMaxCost": 50, + "FinalChapterMinCost": 40, + "HighestChapterCost": 40, + "LowestChapterCost": 10, + "NoPaintingSkips": false, + "death_link": false, + "priority_locations": [], + "progression_balancing": "random-range-low-10-30", + }, + }, + "x-options-sync": { + "A Hat in Time": { + "+start_inventory_from_pool": { + "Badge Pin": 1, + }, + "+triggers": [ + { + "option_category": "A Hat in Time", + "option_name": "EndGoal", + "option_result": "finale", + "options": { + "A Hat in Time": { + "EnableDLC2": false, + "FinalChapterMaxCost": 35, + "FinalChapterMinCost": 25, + "MaxPonCost": 100, + "MinPonCost": 30, + }, + }, + }, + ], + }, + }, +} +`; diff --git a/test/js/bun/yaml/fixtures/AHatInTime.yaml b/test/js/bun/yaml/fixtures/AHatInTime.yaml new file mode 100644 index 0000000000..155a2491e4 --- /dev/null +++ b/test/js/bun/yaml/fixtures/AHatInTime.yaml @@ -0,0 +1,167 @@ +game: &AHiT "A Hat in Time" + +name: "niyrme-AHiT{NUMBER}" + +requires: + version: 0.6.2 + +*AHiT : + # game + progression_balancing: "random-range-middle-40-50" + accessibility: + "full": 1 + "minimal": 0 + death_link: false + + # general + &EndGoal EndGoal: + &EndGoal_Finale finale: 1 + &EndGoal_Rush rush_hour: 0 + &EndGoal_Seal seal_the_deal: 0 + ShuffleStorybookPages: true + ShuffleAlpineZiplines: true + ShuffleSubconPaintings: true + ShuffleActContracts: true + MinPonCost: 20 + MaxPonCost: 80 + BadgeSellerMinItems: 5 + BadgeSellerMaxItems: 8 + # https://docs.google.com/document/d/1x9VLSQ5davfx1KGamR9T0mD5h69_lDXJ6H7Gq7knJRI + LogicDifficulty: "moderate" + NoPaintingSkips: true + CTRLogic: + "time_stop_only": 0 + "scooter": 1 + "sprint": 0 + "nothing": 0 + + # acts + ActRandomizer: "insanity" + StartingChapter: + 1: 1 + 2: 1 + 3: 1 + LowestChapterCost: 5 + HighestChapterCost: 25 + ChapterCostIncrement: 5 + ChapterCostMinDifference: 5 + &GoalMinCost FinalChapterMinCost: 30 + &GoalMaxCost FinalChapterMaxCost: 35 + FinaleShuffle: false + + # items + StartWithCompassBadge: true + CompassBadgeMode: "closest" + RandomizeHatOrder: "time_stop_last" + YarnAvailable: "random-range-middle-40-50" + YarnCostMin: 5 + YarnCostMax: 8 + MinExtraYarn: "random-range-middle-5-15" + HatItems: true + UmbrellaLogic: true + MaxExtraTimePieces: "random-range-high-5-8" + YarnBalancePercent: 25 + TimePieceBalancePercent: "random-range-low-20-35" + + # DLC: Seal the Deal + EnableDLC1: false + Tasksanity: false + TasksanityTaskStep: 1 + TasksanityCheckCount: 18 + ShipShapeCustomTaskGoal: 0 + ExcludeTour: false + + # DLC: Nyakuza Metro + &DLCNyakuza EnableDLC2: true + MetroMinPonCost: 10 + MetroMaxPonCost: 50 + NyakuzaThugMinShopItems: 1 + NyakuzaThugMaxShopItems: 4 + BaseballBat: true + NoTicketSkips: "rush_hour" + + # Death Wish + EnableDeathWish: false + DWTimePieceRequirement: 15 + DWShuffle: false + DWShuffleCountMin: 18 + DWShuffleCountMax: 25 + DWEnableBonus: false + DWAutoCompleteBonuses: true + DWExcludeAnnoyingContracts: true + DWExcludeAnnoyingBonuses: true + DWExcludeCandles: true + DeathWishOnly: false + + # traps + TrapChance: 0 + BabyTrapWeight: 0 + LaserTrapWeight: 0 + ParadeTrapWeight: 0 + + # plando, item & location options + non_local_items: + - "Hookshot Badge" + - "Umbrella" + - "Dweller Mask" + start_inventory_from_pool: + "Sprint Hat": 1 + exclude_locations: + - "Queen Vanessa's Manor - Bedroom Chest" + - "Queen Vanessa's Manor - Hall Chest" + - "Act Completion (The Big Parade)" + priority_locations: + - "Act Completion (Award Ceremony)" + - "Badge Seller - Item 1" + - "Badge Seller - Item 2" + - "Mafia Boss Shop Item" + # Nyakuza DLC + - "Bluefin Tunnel Thug - Item 1" + - "Green Clean Station Thug A - Item 1" + - "Green Clean Station Thug B - Item 1" + - "Main Station Thug A - Item 1" + - "Main Station Thug B - Item 1" + - "Main Station Thug C - Item 1" + - "Pink Paw Station Thug - Item 1" + - "Yellow Overpass Thug A - Item 1" + - "Yellow Overpass Thug B - Item 1" + - "Yellow Overpass Thug C - Item 1" + + ActPlando: + "Dead Bird Studio Basement": "The Big Parade" + +x-options-sync: + *AHiT : + +start_inventory_from_pool: + "Badge Pin": 1 + +triggers: + - option_category: *AHiT + option_name: *EndGoal + option_result: *EndGoal_Finale + options: + *AHiT : + MinPonCost: 30 + MaxPonCost: 100 + *GoalMinCost : 25 + *GoalMaxCost : 35 + *DLCNyakuza : false + +x-options-async: + *AHiT : + progression_balancing: "random-range-low-10-30" + death_link: false + LowestChapterCost: 10 + HighestChapterCost: 40 + ChapterCostIncrement: 7 + ChapterCostMinDifference: 7 + *EndGoal : + *EndGoal_Finale : 9 + *EndGoal_Rush : 1 + *EndGoal_Seal : 0 + NoPaintingSkips: false + *GoalMinCost : 40 + *GoalMaxCost : 50 + +non_local_items: + - "Brewing Hat" + - "Ice Hat" + priority_locations: [] \ No newline at end of file diff --git a/test/js/bun/yaml/translate_yaml_test_suite_to_bun.py b/test/js/bun/yaml/translate_yaml_test_suite_to_bun.py new file mode 100644 index 0000000000..002af37c51 --- /dev/null +++ b/test/js/bun/yaml/translate_yaml_test_suite_to_bun.py @@ -0,0 +1,1049 @@ +#!/usr/bin/env python3 + +import os +import json +import glob +import yaml +import subprocess +import sys +import re +import argparse + +def escape_js_string(s): + """Escape a string for use in JavaScript string literals.""" + result = [] + for char in s: + if char == '\\': + result.append('\\\\') + elif char == '"': + result.append('\\"') + elif char == '\n': + result.append('\\n') + elif char == '\t': + result.append('\\t') + elif char == '\r': + result.append('\\r') + elif char == '\b': + result.append('\\b') + elif char == '\f': + result.append('\\f') + elif ord(char) < 0x20 or ord(char) == 0x7F: + # Control characters - use \xNN notation + result.append(f'\\x{ord(char):02x}') + else: + result.append(char) + return ''.join(result) + +def format_js_string(content): + """Format content for JavaScript string literal.""" + # For JavaScript we'll use template literals for multiline strings + # unless they contain backticks or ${ + if '`' in content or '${' in content: + # Use regular string with escaping + escaped = escape_js_string(content) + return f'"{escaped}"' + elif '\n' in content: + # Use template literal for multiline + # But we still need to escape backslashes + escaped_content = content.replace('\\', '\\\\') # Escape backslashes + return f'`{escaped_content}`' + else: + # Short single line - use regular string + escaped = escape_js_string(content) + return f'"{escaped}"' + +def has_anchors_or_aliases(yaml_content): + """Check if YAML content has anchors (&) or aliases (*).""" + return '&' in yaml_content or '*' in yaml_content + +def stringify_map_keys(obj, from_yaml_package=False): + """Recursively stringify all map keys to match Bun's YAML behavior. + + Args: + obj: The object to process + from_yaml_package: If True, empty string keys came from yaml package converting null keys + and should be converted to "null". If False, empty strings are intentional. + """ + if isinstance(obj, dict): + new_dict = {} + for key, value in obj.items(): + # Convert key to string + if key is None: + # Actual None/null key should become "null" + str_key = "null" + elif key == "" and from_yaml_package: + # Empty string from yaml package (was originally a null key in YAML) + # should be converted to "null" to match Bun's behavior + str_key = "null" + elif key == "": + # Empty string from official test JSON or explicit empty string + # should stay as empty string + str_key = "" + elif isinstance(key, str) and from_yaml_package: + # Check if this is a stringified collection from yaml package + # yaml package converts [a, b] to "[ a, b ]" but Bun/JS uses "a,b" + if key.startswith('[ ') and key.endswith(' ]'): + # This looks like a stringified array from yaml package + # Extract the content and convert to JS array.toString() format + inner = key[2:-2] # Remove "[ " and " ]" + # Split by comma and space, then join with just comma + elements = [] + for elem in inner.split(','): + elem = elem.strip() + # Remove anchor notation (&name) from the element + # Anchors appear as "&name value" in the stringified form + if '&' in elem: + # Remove the anchor part (e.g., "&b b" becomes "b") + parts = elem.split() + if len(parts) > 1 and parts[0].startswith('&'): + elem = ' '.join(parts[1:]) + elements.append(elem) + str_key = ','.join(elements) + elif key.startswith('{ ') and key.endswith(' }'): + # This looks like a stringified object from yaml package + # JavaScript Object.toString() returns "[object Object]" + str_key = "[object Object]" + elif key.startswith('*'): + # This is an alias reference that wasn't resolved by yaml package + # This shouldn't happen in well-formed output, but handle it + # For now, keep it as-is but this might need special handling + str_key = key + else: + str_key = str(key) + else: + # All other keys get stringified + str_key = str(key) + # Recursively process value + new_dict[str_key] = stringify_map_keys(value, from_yaml_package) + return new_dict + elif isinstance(obj, list): + return [stringify_map_keys(item, from_yaml_package) for item in obj] + else: + return obj + +def json_to_js_literal(obj, indent_level=1, seen_objects=None, var_declarations=None): + """Convert JSON object to JavaScript literal, handling shared references.""" + if seen_objects is None: + seen_objects = {} + if var_declarations is None: + var_declarations = [] + + indent = " " * indent_level + + if obj is None: + return "null" + elif isinstance(obj, bool): + return "true" if obj else "false" + elif isinstance(obj, (int, float)): + # Handle special float values + if obj != obj: # NaN + return "NaN" + elif obj == float('inf'): + return "Infinity" + elif obj == float('-inf'): + return "-Infinity" + return str(obj) + elif isinstance(obj, str): + escaped = escape_js_string(obj) + return f'"{escaped}"' + elif isinstance(obj, list): + if len(obj) == 0: + return "[]" + + # Check for complex nested structures + if any(isinstance(item, (list, dict)) for item in obj): + items = [] + for item in obj: + item_str = json_to_js_literal(item, indent_level + 1, seen_objects, var_declarations) + items.append(f"{indent} {item_str}") + return "[\n" + ",\n".join(items) + f"\n{indent}]" + else: + # Simple array - inline + items = [json_to_js_literal(item, indent_level, seen_objects, var_declarations) for item in obj] + return "[" + ", ".join(items) + "]" + elif isinstance(obj, dict): + if len(obj) == 0: + return "{}" + + # Check if this is a simple object + is_simple = all(not isinstance(v, (list, dict)) for v in obj.values()) + + if is_simple and len(obj) <= 3: + # Simple object - inline + pairs = [] + for key, value in obj.items(): + if key.isidentifier() and not key.startswith('$'): + key_str = key + else: + key_str = f'"{escape_js_string(key)}"' + value_str = json_to_js_literal(value, indent_level, seen_objects, var_declarations) + pairs.append(f"{key_str}: {value_str}") + return "{ " + ", ".join(pairs) + " }" + else: + # Complex object - multiline + pairs = [] + for key, value in obj.items(): + if key.isidentifier() and not key.startswith('$'): + key_str = key + else: + key_str = f'"{escape_js_string(key)}"' + value_str = json_to_js_literal(value, indent_level + 1, seen_objects, var_declarations) + pairs.append(f"{indent} {key_str}: {value_str}") + return "{\n" + ",\n".join(pairs) + f"\n{indent}}}" + else: + # Fallback + return json.dumps(obj) + +def parse_test_events(event_file): + """Parse test.event file to infer expected JSON structure. + + Event format: + +STR - Stream start + +DOC - Document start + +MAP - Map start + +SEQ - Sequence start + =VAL - Value (scalar) + =ALI - Alias + -MAP - Map end + -SEQ - Sequence end + -DOC - Document end + -STR - Stream end + """ + if not os.path.exists(event_file): + return None + + with open(event_file, 'r', encoding='utf-8') as f: + lines = f.readlines() + + docs = [] + stack = [] + current_doc = None + in_key = False + pending_key = None + + for line in lines: + line = line.rstrip('\n') + if not line: + continue + + if line.startswith('+DOC'): + stack = [] + current_doc = None + in_key = False + pending_key = None + + elif line.startswith('+MAP'): + new_map = {} + if stack: + parent = stack[-1] + if isinstance(parent, list): + parent.append(new_map) + elif isinstance(parent, dict) and pending_key is not None: + parent[pending_key] = new_map + pending_key = None + in_key = False + else: + current_doc = new_map + stack.append(new_map) + + elif line.startswith('+SEQ'): + new_seq = [] + if stack: + parent = stack[-1] + if isinstance(parent, list): + parent.append(new_seq) + elif isinstance(parent, dict) and pending_key is not None: + parent[pending_key] = new_seq + pending_key = None + in_key = False + else: + current_doc = new_seq + stack.append(new_seq) + + elif line.startswith('=VAL'): + # Extract value after =VAL + value = line[4:].strip() + if value.startswith(':'): + value = value[1:].strip() if len(value) > 1 else '' + + # Convert special values + if value == '': + value = '' + elif value == '': + value = ' ' + + if stack: + parent = stack[-1] + if isinstance(parent, list): + parent.append(value) + elif isinstance(parent, dict): + if in_key or pending_key is None: + # This is a key + pending_key = value + in_key = False + else: + # This is a value for the pending key + parent[pending_key] = value + pending_key = None + else: + # Scalar document + current_doc = value + + elif line.startswith('-MAP') or line.startswith('-SEQ'): + if stack: + completed = stack.pop() + # If this was the last item and we have a pending key, it means empty value + if isinstance(stack[-1] if stack else None, dict) and pending_key is not None: + (stack[-1] if stack else {})[pending_key] = None + pending_key = None + + elif line.startswith('-DOC'): + if current_doc is not None: + docs.append(current_doc) + current_doc = None + stack = [] + pending_key = None + + return docs if docs else None + +def detect_shared_references(yaml_content): + """Detect anchors and their aliases in YAML to identify shared references.""" + # Find all anchors and their aliases + anchor_pattern = r'&(\w+)' + alias_pattern = r'\*(\w+)' + + anchors = re.findall(anchor_pattern, yaml_content) + aliases = re.findall(alias_pattern, yaml_content) + + # Return anchors that are referenced by aliases + shared_refs = [] + for anchor in set(anchors): + if anchor in aliases: + shared_refs.append(anchor) + + return shared_refs + +def generate_expected_with_shared_refs(json_data, yaml_content): + """Generate expected object with shared references for anchors/aliases.""" + shared_refs = detect_shared_references(yaml_content) + + if not shared_refs: + # No shared references, generate simple literal + return json_to_js_literal(json_data) + + # For simplicity, when there are anchors/aliases, we'll generate the expected + # object but note that some values might be shared references + # This is a simplified approach - in reality we'd need to track which values + # are aliased to generate exact shared references + + # Generate with a comment about shared refs + result = json_to_js_literal(json_data) + + # Add comment about shared references + comment = f" // Note: Original YAML has anchors/aliases: {', '.join(shared_refs)}\n" + comment += " // Some values in the parsed result may be shared object references\n" + + return comment + " const expected = " + result + ";" + +def get_expected_from_yaml_parser(yaml_content, use_eemeli_yaml=True): + """Use yaml package (eemeli/yaml) or js-yaml to get expected output.""" + # Create a temporary JavaScript file to parse the YAML + if use_eemeli_yaml: + # Use eemeli/yaml which is more spec-compliant + js_code = f''' +const YAML = require('/Users/dylan/yamlz-3/node_modules/yaml'); + +const input = {format_js_string(yaml_content)}; + +try {{ + const docs = YAML.parseAllDocuments(input); + const results = docs.map(doc => doc.toJSON()); + console.log(JSON.stringify(results)); +}} catch (e) {{ + console.log(JSON.stringify({{"error": e.message}})); +}} +''' + else: + # Fallback to js-yaml + js_code = f''' +const yaml = require('/Users/dylan/yamlz-3/node_modules/js-yaml'); + +const input = {format_js_string(yaml_content)}; + +try {{ + const docs = yaml.loadAll(input); + console.log(JSON.stringify(docs)); +}} catch (e) {{ + console.log(JSON.stringify({{"error": e.message}})); +}} +''' + + # Write to temp file and execute with node + temp_js = '/tmp/parse_yaml_temp.js' + with open(temp_js, 'w') as f: + f.write(js_code) + + try: + result = subprocess.run(['node', temp_js], capture_output=True, text=True, timeout=5) + if result.returncode == 0 and result.stdout.strip(): + output = json.loads(result.stdout.strip()) + if isinstance(output, dict) and 'error' in output: + return None, output['error'] + return output, None + else: + return None, result.stderr or "Failed to parse" + except subprocess.TimeoutExpired: + return None, "Timeout" + except Exception as e: + return None, str(e) + finally: + if os.path.exists(temp_js): + os.remove(temp_js) + +def generate_test(test_dir, test_name, check_ast=True, use_js_yaml=False, use_yaml_pkg=False): + """Generate a single Bun test case from a yaml-test-suite directory. + + Args: + test_dir: Directory containing the test files + test_name: Name for the test + check_ast: If True, validate parsed AST. If False, only check parse success/failure. + use_js_yaml: If True, generate test using js-yaml instead of Bun's YAML + use_yaml_pkg: If True, generate test using yaml package instead of Bun's YAML + """ + + yaml_file = os.path.join(test_dir, "in.yaml") + json_file = os.path.join(test_dir, "in.json") + desc_file = os.path.join(test_dir, "===") + + # Read YAML content + if not os.path.exists(yaml_file): + return None + + with open(yaml_file, 'r', encoding='utf-8') as f: + yaml_content = f.read() + + # Read test description + description = "" + if os.path.exists(desc_file): + with open(desc_file, 'r', encoding='utf-8') as f: + description = f.read().strip().replace('\n', ' ') + + # Check if this is an error test (has 'error' file) + error_file = os.path.join(test_dir, "error") + is_error_test = os.path.exists(error_file) + + # For js-yaml, check if it actually can parse this + js_yaml_fails = False + js_yaml_error_msg = None + if use_js_yaml and not is_error_test: + # Quick check if js-yaml will fail on this + yaml_js_docs, yaml_js_error = get_expected_from_yaml_parser(yaml_content, use_eemeli_yaml=False) + if yaml_js_error: + js_yaml_fails = True + js_yaml_error_msg = yaml_js_error + + # If js-yaml fails but spec says it should pass, generate a special test + if use_js_yaml and js_yaml_fails and not is_error_test: + formatted_content = format_js_string(yaml_content) + return f''' +test.skip("{test_name}", () => {{ + // {description} + // SKIPPED: js-yaml fails but spec says this should pass + // js-yaml error: {js_yaml_error_msg} + const input = {formatted_content}; + + // js-yaml is stricter than the YAML spec - it fails on this valid YAML + // The official test suite says this should parse successfully +}}); +''' + + if is_error_test: + # Generate error test + formatted_content = format_js_string(yaml_content) + if use_js_yaml: + return f''' +test("{test_name}", () => {{ + // {description} + // Error test - expecting parse to fail (using js-yaml) + const input = {formatted_content}; + + expect(() => {{ + return jsYaml.load(input); + }}).toThrow(); +}}); +''' + elif use_yaml_pkg: + return f''' +test("{test_name}", () => {{ + // {description} + // Error test - expecting parse to fail (using yaml package) + const input = {formatted_content}; + + expect(() => {{ + return yamlPkg.parse(input); + }}).toThrow(); +}}); +''' + else: + return f''' +test("{test_name}", () => {{ + // {description} + // Error test - expecting parse to fail + const input: string = {formatted_content}; + + expect(() => {{ + return YAML.parse(input); + }}).toThrow(); +}}); +''' + + # Special handling for known problematic tests + if test_name == "yaml-test-suite/X38W": + # X38W has alias key that creates duplicate - yaml package doesn't handle this correctly + # The correct output is just one key "a,b" with value ["c", "b", "d"] + test = f''' +test("{test_name}", () => {{ + // {description} + // Special case: *a references the same array as first key, creating duplicate key + const input: string = {format_js_string(yaml_content)}; + + const parsed = YAML.parse(input); + + const expected: any = {{ + "a,b": ["c", "b", "d"] + }}; + + expect(parsed).toEqual(expected); +}}); +''' + return test + + # Get expected data from official test suite JSON file if available + json_data = None + has_json = False + + if os.path.exists(json_file): + with open(json_file, 'r', encoding='utf-8') as f: + json_content = f.read().strip() + + if not json_content: + json_data = [None] # Empty file represents null document + has_json = True + else: + try: + # Try single document + single_doc = json.loads(json_content) + json_data = [single_doc] + has_json = True + except json.JSONDecodeError: + # Try to parse as multiple JSON objects concatenated + decoder = json.JSONDecoder() + idx = 0 + docs = [] + while idx < len(json_content): + json_content_from_idx = json_content[idx:].lstrip() + if not json_content_from_idx: + break + try: + obj, end_idx = decoder.raw_decode(json_content_from_idx) + docs.append(obj) + idx += len(json_content[idx:]) - len(json_content_from_idx) + end_idx + except json.JSONDecodeError: + break + + if docs: + json_data = docs + has_json = True + else: + # Last resort: Try multi-document (one JSON per line) + docs = [] + for line in json_content.split('\n'): + line = line.strip() + if line: + try: + docs.append(json.loads(line)) + except: + pass + + if docs: + json_data = docs + has_json = True + + # If no JSON from test suite, use yaml package as reference + # (Skip test.event parsing for now as it's too simplistic) + if not has_json: + yaml_docs, yaml_error = get_expected_from_yaml_parser(yaml_content, use_eemeli_yaml=True) + if yaml_error: + # yaml package couldn't parse it, but maybe Bun's YAML can + # Just check that it doesn't throw + formatted_content = format_js_string(yaml_content) + return f''' +test("{test_name}", () => {{ + // {description} + // Parse test - yaml package couldn't parse, checking YAML behavior + const input = {formatted_content}; + + // Test may pass or fail, we're just documenting behavior + try {{ + const parsed = YAML.parse(input); + // Successfully parsed + expect(parsed).toBeDefined(); + }} catch (e) {{ + // Failed to parse + expect(e).toBeDefined(); + }} +}}); +''' + else: + json_data = yaml_docs + + # If not checking AST, just verify parse success + if not check_ast: + formatted_content = format_js_string(yaml_content) + if use_js_yaml: + return f''' +test("{test_name}", () => {{ + // {description} + // Success test - expecting parse to succeed (AST checking disabled, using js-yaml) + const input = {formatted_content}; + + const parsed = jsYaml.load(input); + expect(parsed).toBeDefined(); +}}); +''' + elif use_yaml_pkg: + return f''' +test("{test_name}", () => {{ + // {description} + // Success test - expecting parse to succeed (AST checking disabled, using yaml package) + const input = {formatted_content}; + + const parsed = yamlPkg.parse(input); + expect(parsed).toBeDefined(); +}}); +''' + else: + return f''' +test("{test_name}", () => {{ + // {description} + // Success test - expecting parse to succeed (AST checking disabled) + const input: string = {formatted_content}; + + const parsed = YAML.parse(input); + expect(parsed).toBeDefined(); +}}); +''' + + # Format the YAML content for JavaScript + formatted_content = format_js_string(yaml_content) + + # Generate the test + comment = f"// {description}" + event_file = os.path.join(test_dir, "test.event") + if not os.path.exists(json_file) and os.path.exists(event_file): + comment += " (using test.event for expected values)" + elif not os.path.exists(json_file): + comment += " (using yaml package for expected values)" + + # Check if YAML has anchors/aliases + has_refs = has_anchors_or_aliases(yaml_content) + + # Handle multi-document YAML + # Only check the actual parsed data to determine if it's multi-document + # Document markers like --- and ... don't reliably indicate multiple documents + is_multi_doc = json_data and len(json_data) > 1 + + if is_multi_doc: + # Multi-document test - YAML.parse will return an array + if use_js_yaml: + test = f''' +test("{test_name}", () => {{ + {comment} + const input: string = {formatted_content}; + + const parsed = jsYaml.loadAll(input); +''' + elif use_yaml_pkg: + test = f''' +test("{test_name}", () => {{ + {comment} + const input: string = {formatted_content}; + + const parsed = yamlPkg.parseAllDocuments(input).map(doc => doc.toJSON()); +''' + else: + test = f''' +test("{test_name}", () => {{ + {comment} + const input: string = {formatted_content}; + + const parsed = YAML.parse(input); +''' + + # Generate expected array + if has_refs: + test += ''' + // Note: Original YAML may have anchors/aliases + // Some values in the parsed result may be shared object references +''' + + # Apply key stringification to match Bun's behavior + # from_yaml_package=!has_json: True if data came from yaml package, False if from official JSON + stringified_data = stringify_map_keys(json_data, from_yaml_package=not has_json) + expected_str = json_to_js_literal(stringified_data) + test += f''' + const expected: any = {expected_str}; + + expect(parsed).toEqual(expected); +}}); +''' + else: + # Single document test + expected_value = json_data[0] if json_data else None + + if use_js_yaml: + test = f''' +test("{test_name}", () => {{ + {comment} + const input: string = {formatted_content}; + + const parsed = jsYaml.load(input); +''' + elif use_yaml_pkg: + test = f''' +test("{test_name}", () => {{ + {comment} + const input: string = {formatted_content}; + + const parsed = yamlPkg.parse(input); +''' + else: + test = f''' +test("{test_name}", () => {{ + {comment} + const input: string = {formatted_content}; + + const parsed = YAML.parse(input); +''' + + # Generate expected value + if has_refs: + # For tests with anchors/aliases, we need to handle shared references + # Check specific patterns in YAML + if '*' in yaml_content and '&' in yaml_content: + # Has both anchors and aliases - need to create shared references + test += ''' + // This YAML has anchors and aliases - creating shared references +''' + # Try to identify simple cases + if 'bill-to: &' in yaml_content and 'ship-to: *' in yaml_content: + # Common pattern: bill-to/ship-to sharing + test += ''' + const sharedAddress: any = ''' + # Find the shared object from expected data + stringified_value = stringify_map_keys(expected_value, from_yaml_package=not has_json) + if isinstance(stringified_value, dict): + if 'bill-to' in stringified_value: + shared_obj = stringified_value.get('bill-to') + test += json_to_js_literal(shared_obj) + ';' + # Now create expected with shared ref + test += ''' + const expected = ''' + # Build object with shared reference + test += '{\n' + for key, value in stringified_value.items(): + if key == 'bill-to': + test += f' "bill-to": sharedAddress,\n' + elif key == 'ship-to' and value == shared_obj: + test += f' "ship-to": sharedAddress,\n' + else: + test += f' "{escape_js_string(key)}": {json_to_js_literal(value)},\n' + test = test.rstrip(',\n') + '\n };' + else: + # Fallback to regular generation + stringified_value = stringify_map_keys(expected_value, from_yaml_package=not has_json) + expected_str = json_to_js_literal(stringified_value) + test += f''' + const expected: any = {expected_str};''' + else: + # Fallback to regular generation + stringified_value = stringify_map_keys(expected_value, from_yaml_package=not has_json) + expected_str = json_to_js_literal(stringified_value) + test += f''' + const expected: any = {expected_str};''' + else: + # Generic anchor/alias case + # Look for patterns like "- &anchor value" and "- *anchor" + anchor_matches = re.findall(r'&(\w+)\s+(.+?)(?:\n|$)', yaml_content) + alias_matches = re.findall(r'\*(\w+)', yaml_content) + + if anchor_matches and alias_matches: + # Build shared values based on anchors + anchor_vars = {} + for anchor_name, _ in anchor_matches: + if anchor_name in [a for a in alias_matches]: + # This anchor is referenced + anchor_vars[anchor_name] = f'shared_{anchor_name}' + + if anchor_vars and isinstance(expected_value, (list, dict)): + # Try to detect which values are shared + test += f''' + // Detected anchors that are referenced: {', '.join(anchor_vars.keys())} +''' + # For now, just generate the expected normally with a note + stringified_value = stringify_map_keys(expected_value, from_yaml_package=not has_json) + expected_str = json_to_js_literal(stringified_value) + test += f''' + const expected: any = {expected_str};''' + else: + stringified_value = stringify_map_keys(expected_value, from_yaml_package=not has_json) + expected_str = json_to_js_literal(stringified_value) + test += f''' + const expected: any = {expected_str};''' + else: + stringified_value = stringify_map_keys(expected_value, from_yaml_package=not has_json) + expected_str = json_to_js_literal(stringified_value) + test += f''' + const expected: any = {expected_str};''' + else: + # Has anchors but no aliases, or vice versa + stringified_value = stringify_map_keys(expected_value, from_yaml_package=not has_json) + expected_str = json_to_js_literal(stringified_value) + test += f''' + const expected: any = {expected_str};''' + else: + # No anchors/aliases - simple case + stringified_value = stringify_map_keys(expected_value, from_yaml_package=not has_json) + expected_str = json_to_js_literal(stringified_value) + test += f''' + const expected: any = {expected_str};''' + + test += ''' + + expect(parsed).toEqual(expected); +}); +''' + + return test + +def main(): + # Parse command-line arguments + parser = argparse.ArgumentParser(description='Translate yaml-test-suite to Bun tests') + parser.add_argument('--no-ast-check', action='store_true', + help='Only check if parsing succeeds/fails, do not validate AST') + parser.add_argument('--with-js-yaml', action='store_true', + help='Also generate a companion test file using js-yaml for validation') + parser.add_argument('--with-yaml', action='store_true', + help='Also generate a companion test file using yaml package for validation') + args = parser.parse_args() + + check_ast = not args.no_ast_check + with_js_yaml = args.with_js_yaml + with_yaml = args.with_yaml + + # Check if yaml package is installed (for getting expected values) + yaml_pkg_found = False + try: + # Try local node_modules first + subprocess.run(['node', '-e', "require('./node_modules/yaml')"], capture_output=True, check=True, cwd='/Users/dylan/yamlz-3') + yaml_pkg_found = True + except: + try: + # Try global install + subprocess.run(['node', '-e', "require('yaml')"], capture_output=True, check=True) + yaml_pkg_found = True + except: + pass + + if not yaml_pkg_found and check_ast: + print("Error: yaml package is not installed. Please run: npm install yaml") + print("Note: yaml package is only required when checking AST. Use --no-ast-check to skip AST validation.") + sys.exit(1) + + # Get all test directories + test_dirs = [] + yaml_test_suite_path = '/Users/dylan/yamlz-3/yaml-test-suite' + + for entry in glob.glob(f'{yaml_test_suite_path}/*'): + if os.path.isdir(entry) and os.path.basename(entry) not in ['.git', 'name', 'tags']: + # Check if this is a test directory (has in.yaml) + if os.path.exists(os.path.join(entry, 'in.yaml')): + test_dirs.append(entry) + else: + # Check for subdirectories with in.yaml (multi-doc tests) + for subdir in glob.glob(os.path.join(entry, '*')): + if os.path.isdir(subdir) and os.path.exists(os.path.join(subdir, 'in.yaml')): + test_dirs.append(subdir) + + test_dirs = sorted(test_dirs) + + print(f"Found {len(test_dirs)} test directories in yaml-test-suite") + if not check_ast: + print("AST checking disabled - will only verify parse success/failure") + + # Generate a sample test first + if test_dirs: + print("\nGenerating sample test...") + # Look for a test with anchors/aliases + sample_dir = None + for td in test_dirs: + yaml_file = os.path.join(td, "in.yaml") + if os.path.exists(yaml_file): + with open(yaml_file, 'r') as f: + content = f.read() + if '&' in content and '*' in content: + sample_dir = td + break + + if not sample_dir: + sample_dir = test_dirs[0] + + test_id = sample_dir.replace(yaml_test_suite_path + '/', '') + test_name = f"yaml-test-suite/{test_id}" + + sample_test = generate_test(sample_dir, test_name, check_ast) + if sample_test: + print(f"Sample test for {test_id}:") + print(sample_test[:800] + "..." if len(sample_test) > 800 else sample_test) + + # Generate all tests + print("\nGenerating all tests...") + + mode_comment = "// AST validation disabled - only checking parse success/failure" if not check_ast else "// Using YAML.parse() with eemeli/yaml package as reference" + + output = f'''// Tests translated from official yaml-test-suite +{mode_comment} +// Total: {len(test_dirs)} test directories + +import {{ test, expect }} from "bun:test"; +import {{ YAML }} from "bun"; + +''' + + successful = 0 + failed = 0 + + for i, test_dir in enumerate(test_dirs): + test_id = test_dir.replace(yaml_test_suite_path + '/', '') + test_name = f"yaml-test-suite/{test_id}" + + if (i + 1) % 50 == 0: + print(f" Processing {i+1}/{len(test_dirs)}...") + + try: + test_case = generate_test(test_dir, test_name, check_ast) + if test_case: + output += test_case + successful += 1 + else: + print(f" Skipped {test_name}: returned None") + failed += 1 + except Exception as e: + print(f" Error with {test_name}: {e}") + failed += 1 + + # Write the output file to Bun's test directory + output_dir = '/Users/dylan/code/bun/test/js/bun/yaml' + os.makedirs(output_dir, exist_ok=True) + + filename = os.path.join(output_dir, 'yaml-test-suite.test.ts') + with open(filename, 'w', encoding='utf-8') as f: + f.write(output) + + print(f"\nGenerated {filename}") + print(f" Successful: {successful} tests") + print(f" Failed/Skipped: {failed} tests") + print(f" Total: {len(test_dirs)} directories processed") + + # Generate js-yaml companion tests if requested + if with_js_yaml: + print("\nGenerating js-yaml companion tests...") + + js_yaml_output = f'''// Tests translated from official yaml-test-suite +// Using js-yaml for validation of test translations +// Total: {len(test_dirs)} test directories + +import {{ test, expect }} from "bun:test"; +const jsYaml = require("js-yaml"); + +''' + + js_yaml_successful = 0 + js_yaml_failed = 0 + + for i, test_dir in enumerate(test_dirs): + test_id = test_dir.replace(yaml_test_suite_path + '/', '') + test_name = f"js-yaml/{test_id}" + + if (i + 1) % 50 == 0: + print(f" Processing js-yaml {i+1}/{len(test_dirs)}...") + + try: + test_case = generate_test(test_dir, test_name, check_ast, use_js_yaml=True) + if test_case: + js_yaml_output += test_case + js_yaml_successful += 1 + else: + js_yaml_failed += 1 + except Exception as e: + print(f" Error with {test_name}: {e}") + js_yaml_failed += 1 + + # Write js-yaml test file + js_yaml_filename = os.path.join(output_dir, 'yaml-test-suite-js-yaml.test.ts') + with open(js_yaml_filename, 'w', encoding='utf-8') as f: + f.write(js_yaml_output) + + print(f"\nGenerated js-yaml companion: {js_yaml_filename}") + print(f" Successful: {js_yaml_successful} tests") + print(f" Failed/Skipped: {js_yaml_failed} tests") + + + # Generate yaml package companion tests if requested + if with_yaml: + print("\nGenerating yaml package companion tests...") + + yaml_output = f'''// Tests translated from official yaml-test-suite +// Using yaml package (eemeli/yaml) for validation of test translations +// Total: {len(test_dirs)} test directories +// Note: Requires 'yaml' package to be installed: npm install yaml + +import {{ test, expect }} from "bun:test"; +import * as yamlPkg from "yaml"; + +''' + + yaml_successful = 0 + yaml_failed = 0 + + for i, test_dir in enumerate(test_dirs): + test_id = test_dir.replace(yaml_test_suite_path + '/', '') + test_name = f"yaml-pkg/{test_id}" + + if (i + 1) % 50 == 0: + print(f" Processing yaml package {i+1}/{len(test_dirs)}...") + + try: + test_case = generate_test(test_dir, test_name, check_ast, use_yaml_pkg=True) + if test_case: + yaml_output += test_case + yaml_successful += 1 + else: + yaml_failed += 1 + except Exception as e: + print(f" Error with {test_name}: {e}") + yaml_failed += 1 + + # Write yaml package test file + yaml_filename = os.path.join(output_dir, 'yaml-test-suite-yaml-pkg.test.ts') + with open(yaml_filename, 'w', encoding='utf-8') as f: + f.write(yaml_output) + + print(f"\nGenerated yaml package companion: {yaml_filename}") + print(f" Successful: {yaml_successful} tests") + print(f" Failed/Skipped: {yaml_failed} tests") + + print(f"\nTo run tests: cd {output_dir} && bun test") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/test/js/bun/yaml/yaml-test-suite.test.ts b/test/js/bun/yaml/yaml-test-suite.test.ts new file mode 100644 index 0000000000..ce10104203 --- /dev/null +++ b/test/js/bun/yaml/yaml-test-suite.test.ts @@ -0,0 +1,6405 @@ +// Tests translated from official yaml-test-suite (6e6c296) +// Using YAML.parse() with eemeli/yaml package as reference +// Total: 402 test directories + +import { YAML } from "bun"; +import { expect, test } from "bun:test"; + +test("yaml-test-suite/229Q", () => { + // Spec Example 2.4. Sequence of Mappings + const input: string = `- + name: Mark McGwire + hr: 65 + avg: 0.278 +- + name: Sammy Sosa + hr: 63 + avg: 0.288 +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { name: "Mark McGwire", hr: 65, avg: 0.278 }, + { name: "Sammy Sosa", hr: 63, avg: 0.288 }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/236B", () => { + // Invalid value after mapping + // Error test - expecting parse to fail + const input: string = `foo: + bar +invalid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/26DV", () => { + // Whitespace around colon in mappings + const input: string = `"top1" : + "key1" : &alias1 scalar1 +'top2' : + 'key2' : &alias2 scalar2 +top3: &node3 + *alias1 : scalar3 +top4: + *alias2 : scalar4 +top5 : + scalar5 +top6: + &anchor6 'key6' : scalar6 +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: alias1, alias2 + + const expected: any = { + top1: { key1: "scalar1" }, + top2: { key2: "scalar2" }, + top3: { scalar1: "scalar3" }, + top4: { scalar2: "scalar4" }, + top5: "scalar5", + top6: { key6: "scalar6" }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/27NA", () => { + // Spec Example 5.9. Directive Indicator + const input: string = `%YAML 1.2 +--- text +`; + + const parsed = YAML.parse(input); + + const expected: any = "text"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/2AUY", () => { + // Tags in Block Sequence + const input: string = ` - !!str a + - b + - !!int 42 + - d +`; + + const parsed = YAML.parse(input); + + const expected: any = ["a", "b", 42, "d"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/2CMS", () => { + // Invalid mapping in plain multiline + // Error test - expecting parse to fail + const input: string = `this + is + invalid: x +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/2EBW", () => { + // Allowed characters in keys + const input: string = + "a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~: safe\n?foo: safe question mark\n:foo: safe colon\n-foo: safe dash\nthis is#not: a comment\n"; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + const expected: any = { + "a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~": "safe", + "?foo": "safe question mark", + ":foo": "safe colon", + "-foo": "safe dash", + "this is#not": "a comment", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/2G84/00", () => { + // Literal modifers + // Error test - expecting parse to fail + const input: string = `--- |0 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/2G84/01", () => { + // Literal modifers + // Error test - expecting parse to fail + const input: string = `--- |10 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/2G84/02", () => { + // Literal modifers + const input: string = "--- |1-"; + + const parsed = YAML.parse(input); + + const expected: any = ""; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/2G84/03", () => { + // Literal modifers + const input: string = "--- |1+"; + + const parsed = YAML.parse(input); + + const expected: any = ""; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/2JQS", () => { + // Block Mapping with Missing Keys (using test.event for expected values) + const input: string = `: a +: b +`; + + const parsed = YAML.parse(input); + + const expected: any = { null: "b" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/2LFX", () => { + // Spec Example 6.13. Reserved Directives [1.3] + const input: string = `%FOO bar baz # Should be ignored + # with a warning. +--- +"foo" +`; + + const parsed = YAML.parse(input); + + const expected: any = "foo"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/2SXE", () => { + // Anchors With Colon in Name + const input: string = `&a: key: &a value +foo: + *a: +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: a + + const expected: any = { key: "value", foo: "key" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/2XXW", () => { + // Spec Example 2.25. Unordered Sets + const input: string = `# Sets are represented as a +# Mapping where each key is +# associated with a null value +--- !!set +? Mark McGwire +? Sammy Sosa +? Ken Griff +`; + + const parsed = YAML.parse(input); + + const expected: any = { "Mark McGwire": null, "Sammy Sosa": null, "Ken Griff": null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/33X3", () => { + // Three explicit integers in a block sequence + const input: string = `--- +- !!int 1 +- !!int -2 +- !!int 33 +`; + + const parsed = YAML.parse(input); + + const expected: any = [1, -2, 33]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/35KP", () => { + // Tags for Root Objects + const input: string = `--- !!map +? a +: b +--- !!seq +- !!str c +--- !!str +d +e +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ a: "b" }, ["c"], "d e"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/36F6", () => { + // Multiline plain scalar with empty line + const input: string = `--- +plain: a + b + + c +`; + + const parsed = YAML.parse(input); + + const expected: any = { plain: "a b\nc" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3ALJ", () => { + // Block Sequence in Block Sequence + const input: string = `- - s1_i1 + - s1_i2 +- s2 +`; + + const parsed = YAML.parse(input); + + const expected: any = [["s1_i1", "s1_i2"], "s2"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3GZX", () => { + // Spec Example 7.1. Alias Nodes + const input: string = `First occurrence: &anchor Foo +Second occurrence: *anchor +Override anchor: &anchor Bar +Reuse anchor: *anchor +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: anchor + + const expected: any = { + "First occurrence": "Foo", + "Second occurrence": "Foo", + "Override anchor": "Bar", + "Reuse anchor": "Bar", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3HFZ", () => { + // Invalid content after document end marker + // Error test - expecting parse to fail + const input: string = `--- +key: value +... invalid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/3MYT", () => { + // Plain Scalar looking like key, comment, anchor and tag + const input: string = `--- +k:#foo + &a !t s +`; + + const parsed = YAML.parse(input); + + const expected: any = "k:#foo &a !t s"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3R3P", () => { + // Single block sequence with anchor + const input: string = `&sequence +- a +`; + + const parsed = YAML.parse(input); + + const expected: any = ["a"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3RLN/00", () => { + // Leading tabs in double quoted + const input: string = `"1 leading + \\ttab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "1 leading \ttab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3RLN/01", () => { + // Leading tabs in double quoted + const input: string = `"2 leading + \\ tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "2 leading \ttab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3RLN/02", () => { + // Leading tabs in double quoted + const input: string = `"3 leading + tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "3 leading tab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3RLN/03", () => { + // Leading tabs in double quoted + const input: string = `"4 leading + \\t tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "4 leading \t tab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3RLN/04", () => { + // Leading tabs in double quoted + const input: string = `"5 leading + \\ tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "5 leading \t tab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3RLN/05", () => { + // Leading tabs in double quoted + const input: string = `"6 leading + tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "6 leading tab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/3UYS", () => { + // Escaped slash in double quotes + const input: string = `escaped slash: "a\\/b" +`; + + const parsed = YAML.parse(input); + + const expected: any = { "escaped slash": "a/b" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4ABK", () => { + // Flow Mapping Separate Values (using test.event for expected values) + const input: string = `{ +unquoted : "separate", +http://foo.com, +omitted value:, +} +`; + + const parsed = YAML.parse(input); + + const expected: any = { unquoted: "separate", "http://foo.com": null, "omitted value": null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4CQQ", () => { + // Spec Example 2.18. Multi-line Flow Scalars + const input: string = `plain: + This unquoted scalar + spans many lines. + +quoted: "So does this + quoted scalar.\\n" +`; + + const parsed = YAML.parse(input); + + const expected: any = { plain: "This unquoted scalar spans many lines.", quoted: "So does this quoted scalar.\n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4EJS", () => { + // Invalid tabs as indendation in a mapping + // Error test - expecting parse to fail + const input: string = `--- +a: + b: + c: value +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/4FJ6", () => { + // Nested implicit complex keys (using test.event for expected values) + const input: string = `--- +[ + [ a, [ [[b,c]]: d, e]]: 23 +] +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { "[\n a,\n [\n {\n ? [ [ b, c ] ]\n : d\n },\n e\n ]\n]": 23 }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4GC6", () => { + // Spec Example 7.7. Single Quoted Characters + const input: string = `'here''s to "quotes"' +`; + + const parsed = YAML.parse(input); + + const expected: any = 'here\'s to "quotes"'; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4H7K", () => { + // Flow sequence with invalid extra closing bracket + // Error test - expecting parse to fail + const input: string = `--- +[ a, b, c ] ] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/4HVU", () => { + // Wrong indendation in Sequence + // Error test - expecting parse to fail + const input: string = `key: + - ok + - also ok + - wrong +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/4JVG", () => { + // Scalar value with two anchors + // Error test - expecting parse to fail + const input: string = `top1: &node1 + &k1 key1: val1 +top2: &node2 + &v2 val2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/4MUZ/00", () => { + // Flow mapping colon on line after key + const input: string = `{"foo" +: "bar"} +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "bar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4MUZ/01", () => { + // Flow mapping colon on line after key + const input: string = `{"foo" +: bar} +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "bar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4MUZ/02", () => { + // Flow mapping colon on line after key + const input: string = `{foo +: bar} +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "bar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4Q9F", () => { + // Folded Block Scalar [1.3] + const input: string = `--- > + ab + cd + + ef + + + gh +`; + + const parsed = YAML.parse(input); + + const expected: any = "ab cd\nef\n\ngh\n"; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/4QFQ", () => { + // Spec Example 8.2. Block Indentation Indicator [1.3] + const input: string = `- | + detected +- > + + + # detected +- |1 + explicit +- > + detected +`; + + const parsed = YAML.parse(input); + + const expected: any = ["detected\n", "\n\n# detected\n", " explicit\n", "detected\n"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4RWC", () => { + // Trailing spaces after flow collection + const input: string = ` [1, 2, 3] + `; + + const parsed = YAML.parse(input); + + const expected: any = [1, 2, 3]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4UYU", () => { + // Colon in Double Quoted String + const input: string = `"foo: bar\\": baz" +`; + + const parsed = YAML.parse(input); + + const expected: any = 'foo: bar": baz'; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4V8U", () => { + // Plain scalar with backslashes + const input: string = `--- +plain\\value\\with\\backslashes +`; + + const parsed = YAML.parse(input); + + const expected: any = "plain\\value\\with\\backslashes"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4WA9", () => { + // Literal scalars + const input: string = `- aaa: |2 + xxx + bbb: | + xxx +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ aaa: "xxx\n", bbb: "xxx\n" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/4ZYM", () => { + // Spec Example 6.4. Line Prefixes + const input: string = `plain: text + lines +quoted: "text + lines" +block: | + text + lines +`; + + const parsed = YAML.parse(input); + + const expected: any = { plain: "text lines", quoted: "text lines", block: "text\n \tlines\n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/52DL", () => { + // Explicit Non-Specific Tag [1.3] + const input: string = `--- +! a +`; + + const parsed = YAML.parse(input); + + const expected: any = "a"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/54T7", () => { + // Flow Mapping + const input: string = `{foo: you, bar: far} +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "you", bar: "far" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/55WF", () => { + // Invalid escape in double quoted string + // Error test - expecting parse to fail + const input: string = `--- +"\\." +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/565N", () => { + // Construct Binary + const input: string = `canonical: !!binary "\\ + R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\\ + OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\\ + +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\\ + AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=" +generic: !!binary | + R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 + OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ + +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC + AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= +description: + The binary value above is a tiny arrow encoded as a gif image. +`; + + const parsed = YAML.parse(input); + + const expected: any = { + canonical: + "R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLCAgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=", + generic: + "R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\nOTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\n+f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\nAgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=\n", + description: "The binary value above is a tiny arrow encoded as a gif image.", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/57H4", () => { + // Spec Example 8.22. Block Collection Nodes + const input: string = `sequence: !!seq +- entry +- !!seq + - nested +mapping: !!map + foo: bar +`; + + const parsed = YAML.parse(input); + + const expected: any = { + sequence: ["entry", ["nested"]], + mapping: { foo: "bar" }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/58MP", () => { + // Flow mapping edge cases + const input: string = `{x: :x} +`; + + const parsed = YAML.parse(input); + + const expected: any = { x: ":x" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/5BVJ", () => { + // Spec Example 5.7. Block Scalar Indicators + const input: string = `literal: | + some + text +folded: > + some + text +`; + + const parsed = YAML.parse(input); + + const expected: any = { literal: "some\ntext\n", folded: "some text\n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/5C5M", () => { + // Spec Example 7.15. Flow Mappings + const input: string = `- { one : two , three: four , } +- {five: six,seven : eight} +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { one: "two", three: "four" }, + { five: "six", seven: "eight" }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/5GBF", () => { + // Spec Example 6.5. Empty Lines + const input: string = `Folding: + "Empty line + + as a line feed" +Chomping: | + Clipped empty lines + + +`; + + const parsed = YAML.parse(input); + + const expected: any = { Folding: "Empty line\nas a line feed", Chomping: "Clipped empty lines\n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/5KJE", () => { + // Spec Example 7.13. Flow Sequence + const input: string = `- [ one, two, ] +- [three ,four] +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + ["one", "two"], + ["three", "four"], + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/5LLU", () => { + // Block scalar with wrong indented line after spaces only + // Error test - expecting parse to fail + const input: string = `block scalar: > + + + + invalid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/5MUD", () => { + // Colon and adjacent value on next line + const input: string = `--- +{ "foo" + :bar } +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "bar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/5NYZ", () => { + // Spec Example 6.9. Separated Comment + const input: string = `key: # Comment + value +`; + + const parsed = YAML.parse(input); + + const expected: any = { key: "value" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/5T43", () => { + // Colon at the beginning of adjacent flow scalar + const input: string = `- { "key":value } +- { "key"::value } +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ key: "value" }, { key: ":value" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/5TRB", () => { + // Invalid document-start marker in doublequoted tring + // Error test - expecting parse to fail + const input: string = `--- +" +--- +" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/5TYM", () => { + // Spec Example 6.21. Local Tag Prefix + const input: string = `%TAG !m! !my- +--- # Bulb here +!m!light fluorescent +... +%TAG !m! !my- +--- # Color here +!m!light green +`; + + const parsed = YAML.parse(input); + + const expected: any = ["fluorescent", "green"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/5U3A", () => { + // Sequence on same Line as Mapping Key + // Error test - expecting parse to fail + const input: string = `key: - a + - b +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/5WE3", () => { + // Spec Example 8.17. Explicit Block Mapping Entries + const input: string = `? explicit key # Empty value +? | + block key +: - one # Explicit compact + - two # block value +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "explicit key": null, + "block key\n": ["one", "two"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/62EZ", () => { + // Invalid block mapping key on same line as previous key + // Error test - expecting parse to fail + const input: string = `--- +x: { y: z }in: valid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/652Z", () => { + // Question mark at start of flow key + const input: string = `{ ?foo: bar, +bar: 42 +} +`; + + const parsed = YAML.parse(input); + + const expected: any = { "?foo": "bar", bar: 42 }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/65WH", () => { + // Single Entry Block Sequence + const input: string = `- foo +`; + + const parsed = YAML.parse(input); + + const expected: any = ["foo"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6BCT", () => { + // Spec Example 6.3. Separation Spaces + const input: string = `- foo: bar +- - baz + - baz +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ foo: "bar" }, ["baz", "baz"]]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6BFJ", () => { + // Mapping, key and flow sequence item anchors (using test.event for expected values) + const input: string = `--- +&mapping +&key [ &item a, b, c ]: value +`; + + const parsed = YAML.parse(input); + + const expected: any = { "a,b,c": "value" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6CA3", () => { + // Tab indented top flow + const input: string = ` [ + ] +`; + + const parsed = YAML.parse(input); + + const expected: any = []; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6CK3", () => { + // Spec Example 6.26. Tag Shorthands + const input: string = `%TAG !e! tag:example.com,2000:app/ +--- +- !local foo +- !!str bar +- !e!tag%21 baz +`; + + const parsed = YAML.parse(input); + + const expected: any = ["foo", "bar", "baz"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6FWR", () => { + // Block Scalar Keep + const input: string = `--- |+ + ab + + +... +`; + + const parsed = YAML.parse(input); + + const expected: any = "ab\n\n \n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6H3V", () => { + // Backslashes in singlequotes + const input: string = `'foo: bar\\': baz' +`; + + const parsed = YAML.parse(input); + + const expected: any = { "foo: bar\\": "baz'" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6HB6", () => { + // Spec Example 6.1. Indentation Spaces + const input: string = ` # Leading comment line spaces are + # neither content nor indentation. + +Not indented: + By one space: | + By four + spaces + Flow style: [ # Leading spaces + By two, # in flow style + Also by two, # are neither + Still by two # content nor + ] # indentation. +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "Not indented": { + "By one space": "By four\n spaces\n", + "Flow style": ["By two", "Also by two", "Still by two"], + }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6JQW", () => { + // Spec Example 2.13. In literals, newlines are preserved + const input: string = `# ASCII Art +--- | + \\//||\\/|| + // || ||__ +`; + + const parsed = YAML.parse(input); + + const expected: any = "\\//||\\/||\n// || ||__\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6JTT", () => { + // Flow sequence without closing bracket + // Error test - expecting parse to fail + const input: string = `--- +[ [ a, b, c ] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/6JWB", () => { + // Tags for Block Objects + const input: string = `foo: !!seq + - !!str a + - !!map + key: !!str value +`; + + const parsed = YAML.parse(input); + + const expected: any = { + foo: ["a", { key: "value" }], + }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/6KGN", () => { + // Anchor for empty node + const input: string = `--- +a: &anchor +b: *anchor +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: anchor + + const expected: any = { a: null, b: null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6LVF", () => { + // Spec Example 6.13. Reserved Directives + const input: string = `%FOO bar baz # Should be ignored + # with a warning. +--- "foo" +`; + + const parsed = YAML.parse(input); + + const expected: any = "foo"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6M2F", () => { + // Aliases in Explicit Block Mapping (using test.event for expected values) + const input: string = `? &a a +: &b b +: *a +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: a + + const expected: any = { a: "b", null: "a" }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/6PBE", () => { + // Zero-indented sequences in explicit mapping keys (using test.event for expected values) + const input: string = `--- +? +- a +- b +: +- c +- d +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "a,b": ["c", "d"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6S55", () => { + // Invalid scalar at the end of sequence + // Error test - expecting parse to fail + const input: string = `key: + - bar + - baz + invalid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/6SLA", () => { + // Allowed characters in quoted mapping key + const input: string = `"foo\\nbar:baz\\tx \\\\$%^&*()x": 23 +'x\\ny:z\\tx $%^&*()x': 24 +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + const expected: any = { "foo\nbar:baz\tx \\$%^&*()x": 23, "x\\ny:z\\tx $%^&*()x": 24 }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/6VJK", () => { + // Spec Example 2.15. Folded newlines are preserved for "more indented" and blank lines + const input: string = `> + Sammy Sosa completed another + fine season with great stats. + + 63 Home Runs + 0.288 Batting Average + + What a year! +`; + + const parsed = YAML.parse(input); + + const expected: any = + "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6WLZ", () => { + // Spec Example 6.18. Primary Tag Handle [1.3] + const input: string = `# Private +--- +!foo "bar" +... +# Global +%TAG ! tag:example.com,2000:app/ +--- +!foo "bar" +`; + + const parsed = YAML.parse(input); + + const expected: any = ["bar", "bar"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6WPF", () => { + // Spec Example 6.8. Flow Folding [1.3] + const input: string = `--- +" + foo + + bar + + baz +" +`; + + const parsed = YAML.parse(input); + + const expected: any = " foo\nbar\nbaz "; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6XDY", () => { + // Two document start markers + const input: string = `--- +--- +`; + + const parsed = YAML.parse(input); + + const expected: any = [null, null]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/6ZKB", () => { + // Spec Example 9.6. Stream + const input: string = `Document +--- +# Empty +... +%YAML 1.2 +--- +matches %: 20 +`; + + const parsed = YAML.parse(input); + + const expected: any = ["Document", null, { "matches %": 20 }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/735Y", () => { + // Spec Example 8.20. Block Node Types + const input: string = `- + "flow in block" +- > + Block scalar +- !!map # Block collection + foo : bar +`; + + const parsed = YAML.parse(input); + + const expected: any = ["flow in block", "Block scalar\n", { foo: "bar" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/74H7", () => { + // Tags in Implicit Mapping + const input: string = `!!str a: b +c: !!int 42 +e: !!str f +g: h +!!str 23: !!bool false +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: "b", + c: 42, + e: "f", + g: "h", + "23": false, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/753E", () => { + // Block Scalar Strip [1.3] + const input: string = `--- |- + ab + + +... +`; + + const parsed = YAML.parse(input); + + const expected: any = "ab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/7A4E", () => { + // Spec Example 7.6. Double Quoted Lines + const input: string = `" 1st non-empty + + 2nd non-empty + 3rd non-empty " +`; + + const parsed = YAML.parse(input); + + const expected: any = " 1st non-empty\n2nd non-empty 3rd non-empty "; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/7BMT", () => { + // Node and Mapping Key Anchors [1.3] + const input: string = `--- +top1: &node1 + &k1 key1: one +top2: &node2 # comment + key2: two +top3: + &k3 key3: three +top4: &node4 + &k4 key4: four +top5: &node5 + key5: five +top6: &val6 + six +top7: + &val7 seven +`; + + const parsed = YAML.parse(input); + + const expected: any = { + top1: { key1: "one" }, + top2: { key2: "two" }, + top3: { key3: "three" }, + top4: { key4: "four" }, + top5: { key5: "five" }, + top6: "six", + top7: "seven", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/7BUB", () => { + // Spec Example 2.10. Node for ā€œSammy Sosaā€ appears twice in this document + const input: string = `--- +hr: + - Mark McGwire + # Following node labeled SS + - &SS Sammy Sosa +rbi: + - *SS # Subsequent occurrence + - Ken Griffey +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: SS + + const expected: any = { + hr: ["Mark McGwire", "Sammy Sosa"], + rbi: ["Sammy Sosa", "Ken Griffey"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/7FWL", () => { + // Spec Example 6.24. Verbatim Tags + const input: string = `! foo : + ! baz +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "baz" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/7LBH", () => { + // Multiline double quoted implicit keys + // Error test - expecting parse to fail + const input: string = `"a\\nb": 1 +"c + d": 1 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/7MNF", () => { + // Missing colon + // Error test - expecting parse to fail + const input: string = `top1: + key1: val1 +top2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/7T8X", () => { + // Spec Example 8.10. Folded Lines - 8.13. Final Empty Lines + const input: string = `> + + folded + line + + next + line + * bullet + + * list + * lines + + last + line + +# Comment +`; + + const parsed = YAML.parse(input); + + const expected: any = "\nfolded line\nnext line\n * bullet\n\n * list\n * lines\n\nlast line\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/7TMG", () => { + // Comment in flow sequence before comma + const input: string = `--- +[ word1 +# comment +, word2] +`; + + const parsed = YAML.parse(input); + + const expected: any = ["word1", "word2"]; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/7W2P", () => { + // Block Mapping with Missing Values + const input: string = `? a +? b +c: +`; + + const parsed = YAML.parse(input); + + const expected: any = { a: null, b: null, c: null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/7Z25", () => { + // Bare document after document end marker + const input: string = `--- +scalar1 +... +key: value +`; + + const parsed = YAML.parse(input); + + const expected: any = ["scalar1", { key: "value" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/7ZZ5", () => { + // Empty flow collections + const input: string = `--- +nested sequences: +- - - [] +- - - {} +key1: [] +key2: {} +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "nested sequences": [[[[]]], [[{}]]], + key1: [], + key2: {}, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/82AN", () => { + // Three dashes and content without space + const input: string = `---word1 +word2 +`; + + const parsed = YAML.parse(input); + + const expected: any = "---word1 word2"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/87E4", () => { + // Spec Example 7.8. Single Quoted Implicit Keys + const input: string = `'implicit block key' : [ + 'implicit flow key' : value, + ] +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "implicit block key": [{ "implicit flow key": "value" }], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/8CWC", () => { + // Plain mapping key ending with colon + const input: string = `--- +key ends with two colons::: value +`; + + const parsed = YAML.parse(input); + + const expected: any = { "key ends with two colons::": "value" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/8G76", () => { + // Spec Example 6.10. Comment Lines + const input: string = ` # Comment + + + +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/8KB6", () => { + // Multiline plain flow mapping key without value + const input: string = `--- +- { single line, a: b} +- { multi + line, a: b} +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { "single line": null, a: "b" }, + { "multi line": null, a: "b" }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/8MK2", () => { + // Explicit Non-Specific Tag + const input: string = `! a +`; + + const parsed = YAML.parse(input); + + const expected: any = "a"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/8QBE", () => { + // Block Sequence in Block Mapping + const input: string = `key: + - item1 + - item2 +`; + + const parsed = YAML.parse(input); + + const expected: any = { + key: ["item1", "item2"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/8UDB", () => { + // Spec Example 7.14. Flow Sequence Entries + const input: string = `[ +"double + quoted", 'single + quoted', +plain + text, [ nested ], +single: pair, +] +`; + + const parsed = YAML.parse(input); + + const expected: any = ["double quoted", "single quoted", "plain text", ["nested"], { single: "pair" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/8XDJ", () => { + // Comment in plain multiline value + // Error test - expecting parse to fail + const input: string = `key: word1 +# xxx + word2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/8XYN", () => { + // Anchor with unicode character + const input: string = `--- +- &😁 unicode anchor +`; + + const parsed = YAML.parse(input); + + const expected: any = ["unicode anchor"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/93JH", () => { + // Block Mappings in Block Sequence + const input: string = ` - key: value + key2: value2 + - + key3: value3 +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ key: "value", key2: "value2" }, { key3: "value3" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/93WF", () => { + // Spec Example 6.6. Line Folding [1.3] + const input: string = `--- >- + trimmed + + + + as + space +`; + + const parsed = YAML.parse(input); + + const expected: any = "trimmed\n\n\nas space"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/96L6", () => { + // Spec Example 2.14. In the folded scalars, newlines become spaces + const input: string = `--- > + Mark McGwire's + year was crippled + by a knee injury. +`; + + const parsed = YAML.parse(input); + + const expected: any = "Mark McGwire's year was crippled by a knee injury.\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/96NN/00", () => { + // Leading tab content in literals + const input: string = `foo: |- + bar +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "\tbar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/96NN/01", () => { + // Leading tab content in literals + const input: string = `foo: |- + bar`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "\tbar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/98YD", () => { + // Spec Example 5.5. Comment Indicator + const input: string = `# Comment only. +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9BXH", () => { + // Multiline doublequoted flow mapping key without value + const input: string = `--- +- { "single line", a: b} +- { "multi + line", a: b} +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { "single line": null, a: "b" }, + { "multi line": null, a: "b" }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9C9N", () => { + // Wrong indented flow sequence + // Error test - expecting parse to fail + const input: string = `--- +flow: [a, +b, +c] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/9CWY", () => { + // Invalid scalar at the end of mapping + // Error test - expecting parse to fail + const input: string = `key: + - item1 + - item2 +invalid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/9DXL", () => { + // Spec Example 9.6. Stream [1.3] + const input: string = `Mapping: Document +--- +# Empty +... +%YAML 1.2 +--- +matches %: 20 +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ Mapping: "Document" }, null, { "matches %": 20 }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9FMG", () => { + // Multi-level Mapping Indent + const input: string = `a: + b: + c: d + e: + f: g +h: i +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: { + b: { c: "d" }, + e: { f: "g" }, + }, + h: "i", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9HCY", () => { + // Need document footer before directives + // Error test - expecting parse to fail + const input: string = `!foo "bar" +%TAG ! tag:example.com,2000:app/ +--- +!foo "bar" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/9J7A", () => { + // Simple Mapping Indent + const input: string = `foo: + bar: baz +`; + + const parsed = YAML.parse(input); + + const expected: any = { + foo: { bar: "baz" }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9JBA", () => { + // Invalid comment after end of flow sequence + // Error test - expecting parse to fail + const input: string = `--- +[ a, b, c, ]#invalid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/9KAX", () => { + // Various combinations of tags and anchors + const input: string = `--- +&a1 +!!str +scalar1 +--- +!!str +&a2 +scalar2 +--- +&a3 +!!str scalar3 +--- +&a4 !!map +&a5 !!str key5: value4 +--- +a6: 1 +&anchor6 b6: 2 +--- +!!map +&a8 !!str key8: value7 +--- +!!map +!!str &a10 key10: value9 +--- +!!str &a11 +value11 +`; + + const parsed = YAML.parse(input); + + // Note: Original YAML may have anchors/aliases + // Some values in the parsed result may be shared object references + + const expected: any = [ + "scalar1", + "scalar2", + "scalar3", + { key5: "value4" }, + { a6: 1, b6: 2 }, + { key8: "value7" }, + { key10: "value9" }, + "value11", + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9KBC", () => { + // Mapping starting at --- line + // Error test - expecting parse to fail + const input: string = `--- key1: value1 + key2: value2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/9MAG", () => { + // Flow sequence with invalid comma at the beginning + // Error test - expecting parse to fail + const input: string = `--- +[ , a, b, c ] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/9MMA", () => { + // Directive by itself with no document + // Error test - expecting parse to fail + const input: string = `%YAML 1.2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/9MMW", () => { + // Single Pair Implicit Entries (using test.event for expected values) + const input: string = `- [ YAML : separate ] +- [ "JSON like":adjacent ] +- [ {JSON: like}:adjacent ] +`; + + const parsed = YAML.parse(input); + + const expected: any = [[{ YAML: "separate" }], [{ "JSON like": "adjacent" }], [{ "[object Object]": "adjacent" }]]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9MQT/00", () => { + // Scalar doc with '...' in content + const input: string = `--- "a +...x +b" +`; + + const parsed = YAML.parse(input); + + const expected: any = "a ...x b"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9MQT/01", () => { + // Scalar doc with '...' in content + // Error test - expecting parse to fail + const input: string = `--- "a +... x +b" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/9SA2", () => { + // Multiline double quoted flow mapping key + const input: string = `--- +- { "single line": value} +- { "multi + line": value} +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ "single line": "value" }, { "multi line": "value" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9SHH", () => { + // Spec Example 5.8. Quoted Scalar Indicators + const input: string = `single: 'text' +double: "text" +`; + + const parsed = YAML.parse(input); + + const expected: any = { single: "text", double: "text" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9TFX", () => { + // Spec Example 7.6. Double Quoted Lines [1.3] + const input: string = `--- +" 1st non-empty + + 2nd non-empty + 3rd non-empty " +`; + + const parsed = YAML.parse(input); + + const expected: any = " 1st non-empty\n2nd non-empty 3rd non-empty "; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9U5K", () => { + // Spec Example 2.12. Compact Nested Mapping + const input: string = `--- +# Products purchased +- item : Super Hoop + quantity: 1 +- item : Basketball + quantity: 4 +- item : Big Shoes + quantity: 1 +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { item: "Super Hoop", quantity: 1 }, + { item: "Basketball", quantity: 4 }, + { item: "Big Shoes", quantity: 1 }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9WXW", () => { + // Spec Example 6.18. Primary Tag Handle + const input: string = `# Private +!foo "bar" +... +# Global +%TAG ! tag:example.com,2000:app/ +--- +!foo "bar" +`; + + const parsed = YAML.parse(input); + + const expected: any = ["bar", "bar"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/9YRD", () => { + // Multiline Scalar at Top Level + const input: string = `a +b + c +d + +e +`; + + const parsed = YAML.parse(input); + + const expected: any = "a b c d\ne"; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/A2M4", () => { + // Spec Example 6.2. Indentation Indicators + const input: string = `? a +: - b + - - c + - d +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: ["b", ["c", "d"]], + }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/A6F9", () => { + // Spec Example 8.4. Chomping Final Line Break + const input: string = `strip: |- + text +clip: | + text +keep: |+ + text +`; + + const parsed = YAML.parse(input); + + const expected: any = { strip: "text", clip: "text\n", keep: "text\n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/A984", () => { + // Multiline Scalar in Mapping + const input: string = `a: b + c +d: + e + f +`; + + const parsed = YAML.parse(input); + + const expected: any = { a: "b c", d: "e f" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/AB8U", () => { + // Sequence entry that looks like two with wrong indentation + const input: string = `- single multiline + - sequence entry +`; + + const parsed = YAML.parse(input); + + const expected: any = ["single multiline - sequence entry"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/AVM7", () => { + // Empty Stream + const input: string = ""; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/AZ63", () => { + // Sequence With Same Indentation as Parent Mapping + const input: string = `one: +- 2 +- 3 +four: 5 +`; + + const parsed = YAML.parse(input); + + const expected: any = { + one: [2, 3], + four: 5, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/AZW3", () => { + // Lookahead test cases + const input: string = `- bla"keks: foo +- bla]keks: foo +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ 'bla"keks': "foo" }, { "bla]keks": "foo" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/B3HG", () => { + // Spec Example 8.9. Folded Scalar [1.3] + const input: string = `--- > + folded + text + + +`; + + const parsed = YAML.parse(input); + + const expected: any = "folded text\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/B63P", () => { + // Directive without document + // Error test - expecting parse to fail + const input: string = `%YAML 1.2 +... +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/BD7L", () => { + // Invalid mapping after sequence + // Error test - expecting parse to fail + const input: string = `- item1 +- item2 +invalid: x +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/BEC7", () => { + // Spec Example 6.14. ā€œYAMLā€ directive + const input: string = `%YAML 1.3 # Attempt parsing + # with a warning +--- +"foo" +`; + + const parsed = YAML.parse(input); + + const expected: any = "foo"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/BF9H", () => { + // Trailing comment in multiline plain scalar + // Error test - expecting parse to fail + const input: string = `--- +plain: a + b # end of scalar + c +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/BS4K", () => { + // Comment between plain scalar lines + // Error test - expecting parse to fail + const input: string = `word1 # comment +word2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/BU8L", () => { + // Node Anchor and Tag on Seperate Lines + const input: string = `key: &anchor + !!map + a: b +`; + + const parsed = YAML.parse(input); + + const expected: any = { + key: { a: "b" }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/C2DT", () => { + // Spec Example 7.18. Flow Mapping Adjacent Values + const input: string = `{ +"adjacent":value, +"readable": value, +"empty": +} +`; + + const parsed = YAML.parse(input); + + const expected: any = { adjacent: "value", readable: "value", empty: null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/C2SP", () => { + // Flow Mapping Key on two lines + // Error test - expecting parse to fail + const input: string = `[23 +]: 42 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/C4HZ", () => { + // Spec Example 2.24. Global Tags + const input: string = `%TAG ! tag:clarkevans.com,2002: +--- !shape + # Use the ! handle for presenting + # tag:clarkevans.com,2002:circle +- !circle + center: &ORIGIN {x: 73, y: 129} + radius: 7 +- !line + start: *ORIGIN + finish: { x: 89, y: 102 } +- !label + start: *ORIGIN + color: 0xFFEEBB + text: Pretty vector drawing. +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: ORIGIN + + const expected: any = [ + { + center: { x: 73, y: 129 }, + radius: 7, + }, + { + start: { x: 73, y: 129 }, + finish: { x: 89, y: 102 }, + }, + { + start: { x: 73, y: 129 }, + color: 16772795, + text: "Pretty vector drawing.", + }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/CC74", () => { + // Spec Example 6.20. Tag Handles + const input: string = `%TAG !e! tag:example.com,2000:app/ +--- +!e!foo "bar" +`; + + const parsed = YAML.parse(input); + + const expected: any = "bar"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/CFD4", () => { + // Empty implicit key in single pair flow sequences (using test.event for expected values) + const input: string = `- [ : empty key ] +- [: another empty key] +`; + + const parsed = YAML.parse(input); + + const expected: any = [[{ null: "empty key" }], [{ null: "another empty key" }]]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/CML9", () => { + // Missing comma in flow + // Error test - expecting parse to fail + const input: string = `key: [ word1 +# xxx + word2 ] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/CN3R", () => { + // Various location of anchors in flow sequence + const input: string = `&flowseq [ + a: b, + &c c: d, + { &e e: f }, + &g { g: h } +] +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ a: "b" }, { c: "d" }, { e: "f" }, { g: "h" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/CPZ3", () => { + // Doublequoted scalar starting with a tab + const input: string = `--- +tab: "\\tstring" +`; + + const parsed = YAML.parse(input); + + const expected: any = { tab: "\tstring" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/CQ3W", () => { + // Double quoted string without closing quote + // Error test - expecting parse to fail + const input: string = `--- +key: "missing closing quote +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/CT4Q", () => { + // Spec Example 7.20. Single Pair Explicit Entry + const input: string = `[ +? foo + bar : baz +] +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ "foo bar": "baz" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/CTN5", () => { + // Flow sequence with invalid extra comma + // Error test - expecting parse to fail + const input: string = `--- +[ a, b, c, , ] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/CUP7", () => { + // Spec Example 5.6. Node Property Indicators + const input: string = `anchored: !local &anchor value +alias: *anchor +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: anchor + + const expected: any = { anchored: "value", alias: "value" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/CVW2", () => { + // Invalid comment after comma + // Error test - expecting parse to fail + const input: string = `--- +[ a, b, c,#invalid +] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/CXX2", () => { + // Mapping with anchor on document start line + // Error test - expecting parse to fail + const input: string = `--- &anchor a: b +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/D49Q", () => { + // Multiline single quoted implicit keys + // Error test - expecting parse to fail + const input: string = `'a\\nb': 1 +'c + d': 1 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/D83L", () => { + // Block scalar indicator order + const input: string = `- |2- + explicit indent and chomp +- |-2 + chomp and explicit indent +`; + + const parsed = YAML.parse(input); + + const expected: any = ["explicit indent and chomp", "chomp and explicit indent"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/D88J", () => { + // Flow Sequence in Block Mapping + const input: string = `a: [b, c] +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: ["b", "c"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/D9TU", () => { + // Single Pair Block Mapping + const input: string = `foo: bar +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "bar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DBG4", () => { + // Spec Example 7.10. Plain Characters + const input: string = `# Outside flow collection: +- ::vector +- ": - ()" +- Up, up, and away! +- -123 +- http://example.com/foo#bar +# Inside flow collection: +- [ ::vector, + ": - ()", + "Up, up and away!", + -123, + http://example.com/foo#bar ] +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + "::vector", + ": - ()", + "Up, up, and away!", + -123, + "http://example.com/foo#bar", + ["::vector", ": - ()", "Up, up and away!", -123, "http://example.com/foo#bar"], + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DC7X", () => { + // Various trailing tabs + const input: string = `a: b +seq: + - a +c: d #X +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: "b", + seq: ["a"], + c: "d", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DE56/00", () => { + // Trailing tabs in double quoted + const input: string = `"1 trailing\\t + tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "1 trailing\t tab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DE56/01", () => { + // Trailing tabs in double quoted + const input: string = `"2 trailing\\t + tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "2 trailing\t tab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DE56/02", () => { + // Trailing tabs in double quoted + const input: string = `"3 trailing\\ + tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "3 trailing\t tab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DE56/03", () => { + // Trailing tabs in double quoted + const input: string = `"4 trailing\\ + tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "4 trailing\t tab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DE56/04", () => { + // Trailing tabs in double quoted + const input: string = `"5 trailing + tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "5 trailing tab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DE56/05", () => { + // Trailing tabs in double quoted + const input: string = `"6 trailing + tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "6 trailing tab"; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/DFF7", () => { + // Spec Example 7.16. Flow Mapping Entries (using test.event for expected values) + const input: string = `{ +? explicit: entry, +implicit: entry, +? +} +`; + + const parsed = YAML.parse(input); + + const expected: any = { explicit: "entry", implicit: "entry", null: null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DHP8", () => { + // Flow Sequence + const input: string = `[foo, bar, 42] +`; + + const parsed = YAML.parse(input); + + const expected: any = ["foo", "bar", 42]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DK3J", () => { + // Zero indented block scalar with line that looks like a comment + const input: string = `--- > +line1 +# no comment +line3 +`; + + const parsed = YAML.parse(input); + + const expected: any = "line1 # no comment line3\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DK4H", () => { + // Implicit key followed by newline + // Error test - expecting parse to fail + const input: string = `--- +[ key + : value ] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/DK95/00", () => { + // Tabs that look like indentation + const input: string = `foo: + bar +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "bar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DK95/01", () => { + // Tabs that look like indentation + // Error test - expecting parse to fail + const input: string = `foo: "bar + baz" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/DK95/02", () => { + // Tabs that look like indentation + const input: string = `foo: "bar + baz" +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "bar baz" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DK95/03", () => { + // Tabs that look like indentation + const input: string = ` +foo: 1 +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: 1 }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DK95/04", () => { + // Tabs that look like indentation + const input: string = `foo: 1 + +bar: 2 +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: 1, bar: 2 }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DK95/05", () => { + // Tabs that look like indentation + const input: string = `foo: 1 + +bar: 2 +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: 1, bar: 2 }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/DK95/06", () => { + // Tabs that look like indentation + // Error test - expecting parse to fail + const input: string = `foo: + a: 1 + b: 2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/DK95/07", () => { + // Tabs that look like indentation + const input: string = `%YAML 1.2 + +--- +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DK95/08", () => { + // Tabs that look like indentation + const input: string = `foo: "bar + baz " +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "bar baz \t \t " }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/DMG6", () => { + // Wrong indendation in Map + // Error test - expecting parse to fail + const input: string = `key: + ok: 1 + wrong: 2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/DWX9", () => { + // Spec Example 8.8. Literal Content + const input: string = `| + + + literal + + + text + + # Comment +`; + + const parsed = YAML.parse(input); + + const expected: any = "\n\nliteral\n \n\ntext\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/E76Z", () => { + // Aliases in Implicit Block Mapping + const input: string = `&a a: &b b +*b : *a +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: a + + const expected: any = { a: "b", b: "a" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/EB22", () => { + // Missing document-end marker before directive + // Error test - expecting parse to fail + const input: string = `--- +scalar1 # comment +%YAML 1.2 +--- +scalar2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/EHF6", () => { + // Tags for Flow Objects + const input: string = `!!map { + k: !!seq + [ a, !!str b] +} +`; + + const parsed = YAML.parse(input); + + const expected: any = { + k: ["a", "b"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/EW3V", () => { + // Wrong indendation in mapping + // Error test - expecting parse to fail + const input: string = `k1: v1 + k2: v2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/EX5H", () => { + // Multiline Scalar at Top Level [1.3] + const input: string = `--- +a +b + c +d + +e +`; + + const parsed = YAML.parse(input); + + const expected: any = "a b c d\ne"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/EXG3", () => { + // Three dashes and content without space [1.3] + const input: string = `--- +---word1 +word2 +`; + + const parsed = YAML.parse(input); + + const expected: any = "---word1 word2"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/F2C7", () => { + // Anchors and Tags + const input: string = ` - &a !!str a + - !!int 2 + - !!int &c 4 + - &d d +`; + + const parsed = YAML.parse(input); + + const expected: any = ["a", 2, 4, "d"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/F3CP", () => { + // Nested flow collections on one line + const input: string = `--- +{ a: [b, c, { d: [e, f] } ] } +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: [ + "b", + "c", + { + d: ["e", "f"], + }, + ], + }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/F6MC", () => { + // More indented lines at the beginning of folded block scalars + const input: string = `--- +a: >2 + more indented + regular +b: >2 + + + more indented + regular +`; + + const parsed = YAML.parse(input); + + const expected: any = { a: " more indented\nregular\n", b: "\n\n more indented\nregular\n" }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/F8F9", () => { + // Spec Example 8.5. Chomping Trailing Lines + const input: string = ` # Strip + # Comments: +strip: |- + # text + + # Clip + # comments: + +clip: | + # text + + # Keep + # comments: + +keep: |+ + # text + + # Trail + # comments. +`; + + const parsed = YAML.parse(input); + + const expected: any = { strip: "# text", clip: "# text\n", keep: "# text\n\n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/FBC9", () => { + // Allowed characters in plain scalars + const input: string = + "safe: a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\n !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\nsafe question mark: ?foo\nsafe colon: :foo\nsafe dash: -foo\n"; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + const expected: any = { + safe: "a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~ !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~", + "safe question mark": "?foo", + "safe colon": ":foo", + "safe dash": "-foo", + }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/FH7J", () => { + // Tags on Empty Scalars (using test.event for expected values) + const input: string = `- !!str +- + !!null : a + b: !!str +- !!str : !!null +`; + + const parsed = YAML.parse(input); + + const expected: any = ["", { null: "a", b: "" }, { null: null }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/FP8R", () => { + // Zero indented block scalar + const input: string = `--- > +line1 +line2 +line3 +`; + + const parsed = YAML.parse(input); + + const expected: any = "line1 line2 line3\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/FQ7F", () => { + // Spec Example 2.1. Sequence of Scalars + const input: string = `- Mark McGwire +- Sammy Sosa +- Ken Griffey +`; + + const parsed = YAML.parse(input); + + const expected: any = ["Mark McGwire", "Sammy Sosa", "Ken Griffey"]; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/FRK4", () => { + // Spec Example 7.3. Completely Empty Flow Nodes (using test.event for expected values) + const input: string = `{ + ? foo :, + : bar, +} +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: null, null: "bar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/FTA2", () => { + // Single block sequence with anchor and explicit document start + const input: string = `--- &sequence +- a +`; + + const parsed = YAML.parse(input); + + const expected: any = ["a"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/FUP4", () => { + // Flow Sequence in Flow Sequence + const input: string = `[a, [b, c]] +`; + + const parsed = YAML.parse(input); + + const expected: any = ["a", ["b", "c"]]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/G4RS", () => { + // Spec Example 2.17. Quoted Scalars + const input: string = `unicode: "Sosa did fine.\\u263A" +control: "\\b1998\\t1999\\t2000\\n" +hex esc: "\\x0d\\x0a is \\r\\n" + +single: '"Howdy!" he cried.' +quoted: ' # Not a ''comment''.' +tie-fighter: '|\\-*-/|' +`; + + const parsed = YAML.parse(input); + + const expected: any = { + unicode: "Sosa did fine.☺", + control: "\b1998\t1999\t2000\n", + "hex esc": "\r\n is \r\n", + single: '"Howdy!" he cried.', + quoted: " # Not a 'comment'.", + "tie-fighter": "|\\-*-/|", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/G5U8", () => { + // Plain dashes in flow sequence + // Error test - expecting parse to fail + const input: string = `--- +- [-, -] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/G7JE", () => { + // Multiline implicit keys + // Error test - expecting parse to fail + const input: string = `a\\nb: 1 +c + d: 1 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/G992", () => { + // Spec Example 8.9. Folded Scalar + const input: string = `> + folded + text + + +`; + + const parsed = YAML.parse(input); + + const expected: any = "folded text\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/G9HC", () => { + // Invalid anchor in zero indented sequence + // Error test - expecting parse to fail + const input: string = `--- +seq: +&anchor +- a +- b +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/GDY7", () => { + // Comment that looks like a mapping key + // Error test - expecting parse to fail + const input: string = `key: value +this is #not a: key +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/GH63", () => { + // Mixed Block Mapping (explicit to implicit) + const input: string = `? a +: 1.3 +fifteen: d +`; + + const parsed = YAML.parse(input); + + const expected: any = { a: 1.3, fifteen: "d" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/GT5M", () => { + // Node anchor in sequence + // Error test - expecting parse to fail + const input: string = `- item1 +&node +- item2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/H2RW", () => { + // Blank lines + const input: string = `foo: 1 + +bar: 2 + +text: | + a + + b + + c + + d +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: 1, bar: 2, text: "a\n \nb\n\nc\n\nd\n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/H3Z8", () => { + // Literal unicode + const input: string = `--- +wanted: love ♄ and peace ☮ +`; + + const parsed = YAML.parse(input); + + const expected: any = { wanted: "love ♄ and peace ☮" }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/H7J7", () => { + // Node anchor not indented + // Error test - expecting parse to fail + const input: string = `key: &x +!!map + a: b +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/H7TQ", () => { + // Extra words on %YAML directive + // Error test - expecting parse to fail + const input: string = `%YAML 1.2 foo +--- +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/HM87/00", () => { + // Scalars in flow start with syntax char + const input: string = `[:x] +`; + + const parsed = YAML.parse(input); + + const expected: any = [":x"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/HM87/01", () => { + // Scalars in flow start with syntax char + const input: string = `[?x] +`; + + const parsed = YAML.parse(input); + + const expected: any = ["?x"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/HMK4", () => { + // Spec Example 2.16. Indentation determines scope + const input: string = `name: Mark McGwire +accomplishment: > + Mark set a major league + home run record in 1998. +stats: | + 65 Home Runs + 0.278 Batting Average +`; + + const parsed = YAML.parse(input); + + const expected: any = { + name: "Mark McGwire", + accomplishment: "Mark set a major league home run record in 1998.\n", + stats: "65 Home Runs\n0.278 Batting Average\n", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/HMQ5", () => { + // Spec Example 6.23. Node Properties + const input: string = `!!str &a1 "foo": + !!str bar +&a2 baz : *a1 +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: a1 + + const expected: any = { foo: "bar", baz: "foo" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/HRE5", () => { + // Double quoted scalar with escaped single quote + // Error test - expecting parse to fail + const input: string = `--- +double: "quoted \\' scalar" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/HS5T", () => { + // Spec Example 7.12. Plain Lines + const input: string = `1st non-empty + + 2nd non-empty + 3rd non-empty +`; + + const parsed = YAML.parse(input); + + const expected: any = "1st non-empty\n2nd non-empty 3rd non-empty"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/HU3P", () => { + // Invalid Mapping in plain scalar + // Error test - expecting parse to fail + const input: string = `key: + word1 word2 + no: key +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/HWV9", () => { + // Document-end marker + const input: string = `... +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/J3BT", () => { + // Spec Example 5.12. Tabs and Spaces + const input: string = `# Tabs and spaces +quoted: "Quoted " +block: | + void main() { + printf("Hello, world!\\n"); + } +`; + + const parsed = YAML.parse(input); + + const expected: any = { quoted: "Quoted \t", block: 'void main() {\n\tprintf("Hello, world!\\n");\n}\n' }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/J5UC", () => { + // Multiple Pair Block Mapping + const input: string = `foo: blue +bar: arrr +baz: jazz +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "blue", bar: "arrr", baz: "jazz" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/J7PZ", () => { + // Spec Example 2.26. Ordered Mappings + const input: string = `# The !!omap tag is one of the optional types +# introduced for YAML 1.1. In 1.2, it is not +# part of the standard tags and should not be +# enabled by default. +# Ordered maps are represented as +# A sequence of mappings, with +# each mapping having one key +--- !!omap +- Mark McGwire: 65 +- Sammy Sosa: 63 +- Ken Griffy: 58 +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ "Mark McGwire": 65 }, { "Sammy Sosa": 63 }, { "Ken Griffy": 58 }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/J7VC", () => { + // Empty Lines Between Mapping Elements + const input: string = `one: 2 + + +three: 4 +`; + + const parsed = YAML.parse(input); + + const expected: any = { one: 2, three: 4 }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/J9HZ", () => { + // Spec Example 2.9. Single Document with Two Comments + const input: string = `--- +hr: # 1998 hr ranking + - Mark McGwire + - Sammy Sosa +rbi: + # 1998 rbi ranking + - Sammy Sosa + - Ken Griffey +`; + + const parsed = YAML.parse(input); + + const expected: any = { + hr: ["Mark McGwire", "Sammy Sosa"], + rbi: ["Sammy Sosa", "Ken Griffey"], + }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/JEF9/00", () => { + // Trailing whitespace in streams + const input: string = `- |+ + + +`; + + const parsed = YAML.parse(input); + + const expected: any = ["\n\n"]; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/JEF9/01", () => { + // Trailing whitespace in streams + const input: string = `- |+ + +`; + + const parsed = YAML.parse(input); + + const expected: any = ["\n"]; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/JEF9/02", () => { + // Trailing whitespace in streams + const input: string = `- |+ + `; + + const parsed = YAML.parse(input); + + const expected: any = ["\n"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/JHB9", () => { + // Spec Example 2.7. Two Documents in a Stream + const input: string = `# Ranking of 1998 home runs +--- +- Mark McGwire +- Sammy Sosa +- Ken Griffey + +# Team ranking +--- +- Chicago Cubs +- St Louis Cardinals +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + ["Mark McGwire", "Sammy Sosa", "Ken Griffey"], + ["Chicago Cubs", "St Louis Cardinals"], + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/JKF3", () => { + // Multiline unidented double quoted block key + // Error test - expecting parse to fail + const input: string = `- - "bar +bar": x +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/JQ4R", () => { + // Spec Example 8.14. Block Sequence + const input: string = `block sequence: + - one + - two : three +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "block sequence": ["one", { two: "three" }], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/JR7V", () => { + // Question marks in scalars + const input: string = `- a?string +- another ? string +- key: value? +- [a?string] +- [another ? string] +- {key: value? } +- {key: value?} +- {key?: value } +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + "a?string", + "another ? string", + { key: "value?" }, + ["a?string"], + ["another ? string"], + { key: "value?" }, + { key: "value?" }, + { "key?": "value" }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/JS2J", () => { + // Spec Example 6.29. Node Anchors + const input: string = `First occurrence: &anchor Value +Second occurrence: *anchor +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: anchor + + const expected: any = { "First occurrence": "Value", "Second occurrence": "Value" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/JTV5", () => { + // Block Mapping with Multiline Scalars + const input: string = `? a + true +: null + d +? e + 42 +`; + + const parsed = YAML.parse(input); + + const expected: any = { "a true": "null d", "e 42": null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/JY7Z", () => { + // Trailing content that looks like a mapping + // Error test - expecting parse to fail + const input: string = `key1: "quoted1" +key2: "quoted2" no key: nor value +key3: "quoted3" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/K3WX", () => { + // Colon and adjacent value after comment on next line + const input: string = `--- +{ "foo" # comment + :bar } +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "bar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/K4SU", () => { + // Multiple Entry Block Sequence + const input: string = `- foo +- bar +- 42 +`; + + const parsed = YAML.parse(input); + + const expected: any = ["foo", "bar", 42]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/K527", () => { + // Spec Example 6.6. Line Folding + const input: string = `>- + trimmed + + + + as + space +`; + + const parsed = YAML.parse(input); + + const expected: any = "trimmed\n\n\nas space"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/K54U", () => { + // Tab after document header + const input: string = `--- scalar +`; + + const parsed = YAML.parse(input); + + const expected: any = "scalar"; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/K858", () => { + // Spec Example 8.6. Empty Scalar Chomping + const input: string = `strip: >- + +clip: > + +keep: |+ + +`; + + const parsed = YAML.parse(input); + + const expected: any = { strip: "", clip: "", keep: "\n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/KH5V/00", () => { + // Inline tabs in double quoted + const input: string = `"1 inline\\ttab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "1 inline\ttab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/KH5V/01", () => { + // Inline tabs in double quoted + const input: string = `"2 inline\\ tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "2 inline\ttab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/KH5V/02", () => { + // Inline tabs in double quoted + const input: string = `"3 inline tab" +`; + + const parsed = YAML.parse(input); + + const expected: any = "3 inline\ttab"; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/KK5P", () => { + // Various combinations of explicit block mappings (using test.event for expected values) + const input: string = `complex1: + ? - a +complex2: + ? - a + : b +complex3: + ? - a + : > + b +complex4: + ? > + a + : +complex5: + ? - a + : - b +`; + + const parsed = YAML.parse(input); + + const expected: any = { + complex1: { a: null }, + complex2: { a: "b" }, + complex3: { a: "b\n" }, + complex4: { "a\n": null }, + complex5: { + a: ["b"], + }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/KMK3", () => { + // Block Submapping + const input: string = `foo: + bar: 1 +baz: 2 +`; + + const parsed = YAML.parse(input); + + const expected: any = { + foo: { bar: 1 }, + baz: 2, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/KS4U", () => { + // Invalid item after end of flow sequence + // Error test - expecting parse to fail + const input: string = `--- +[ +sequence item +] +invalid item +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/KSS4", () => { + // Scalars on --- line + const input: string = `--- "quoted +string" +--- &node foo +`; + + const parsed = YAML.parse(input); + + // Note: Original YAML may have anchors/aliases + // Some values in the parsed result may be shared object references + + const expected: any = ["quoted string", "foo"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/L24T/00", () => { + // Trailing line of spaces + const input: string = `foo: | + x + +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "x\n \n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/L24T/01", () => { + // Trailing line of spaces + const input: string = `foo: | + x + `; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "x\n \n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/L383", () => { + // Two scalar docs with trailing comments + const input: string = `--- foo # comment +--- foo # comment +`; + + const parsed = YAML.parse(input); + + const expected: any = ["foo", "foo"]; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/L94M", () => { + // Tags in Explicit Mapping + const input: string = `? !!str a +: !!int 47 +? c +: !!str d +`; + + const parsed = YAML.parse(input); + + const expected: any = { a: 47, c: "d" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/L9U5", () => { + // Spec Example 7.11. Plain Implicit Keys + const input: string = `implicit block key : [ + implicit flow key : value, + ] +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "implicit block key": [{ "implicit flow key": "value" }], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/LE5A", () => { + // Spec Example 7.24. Flow Nodes + const input: string = `- !!str "a" +- 'b' +- &anchor "c" +- *anchor +- !!str +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: anchor + + const expected: any = ["a", "b", "c", "c", ""]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/LHL4", () => { + // Invalid tag + // Error test - expecting parse to fail + const input: string = `--- +!invalid{}tag scalar +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/LP6E", () => { + // Whitespace After Scalars in Flow + const input: string = `- [a, b , c ] +- { "a" : b + , c : 'd' , + e : "f" + } +- [ ] +`; + + const parsed = YAML.parse(input); + + const expected: any = [["a", "b", "c"], { a: "b", c: "d", e: "f" }, []]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/LQZ7", () => { + // Spec Example 7.4. Double Quoted Implicit Keys + const input: string = `"implicit block key" : [ + "implicit flow key" : value, + ] +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "implicit block key": [{ "implicit flow key": "value" }], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/LX3P", () => { + // Implicit Flow Mapping Key on one line (using test.event for expected values) + const input: string = `[flow]: block +`; + + const parsed = YAML.parse(input); + + const expected: any = { flow: "block" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/M29M", () => { + // Literal Block Scalar + const input: string = `a: | + ab + + cd + ef + + +... +`; + + const parsed = YAML.parse(input); + + const expected: any = { a: "ab\n\ncd\nef\n" }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/M2N8/00", () => { + // Question mark edge cases (using test.event for expected values) + const input: string = `- ? : x +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ "[object Object]": null }]; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/M2N8/01", () => { + // Question mark edge cases (using test.event for expected values) + const input: string = `? []: x +`; + + const parsed = YAML.parse(input); + + const expected: any = { "{\n ? []\n : x\n}": null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/M5C3", () => { + // Spec Example 8.21. Block Scalar Nodes + const input: string = `literal: |2 + value +folded: + !foo + >1 + value +`; + + const parsed = YAML.parse(input); + + const expected: any = { literal: "value\n", folded: "value\n" }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/M5DY", () => { + // Spec Example 2.11. Mapping between Sequences (using test.event for expected values) + const input: string = `? - Detroit Tigers + - Chicago cubs +: + - 2001-07-23 + +? [ New York Yankees, + Atlanta Braves ] +: [ 2001-07-02, 2001-08-12, + 2001-08-14 ] +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "Detroit Tigers,Chicago cubs": ["2001-07-23"], + "New York Yankees,Atlanta Braves": ["2001-07-02", "2001-08-12", "2001-08-14"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/M6YH", () => { + // Block sequence indentation + const input: string = `- | + x +- + foo: bar +- + - 42 +`; + + const parsed = YAML.parse(input); + + const expected: any = ["x\n", { foo: "bar" }, [42]]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/M7A3", () => { + // Spec Example 9.3. Bare Documents + const input: string = `Bare +document +... +# No document +... +| +%!PS-Adobe-2.0 # Not the first line +`; + + const parsed = YAML.parse(input); + + const expected: any = ["Bare document", "%!PS-Adobe-2.0 # Not the first line\n"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/M7NX", () => { + // Nested flow collections + const input: string = `--- +{ + a: [ + b, c, { + d: [e, f] + } + ] +} +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: [ + "b", + "c", + { + d: ["e", "f"], + }, + ], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/M9B4", () => { + // Spec Example 8.7. Literal Scalar + const input: string = `| + literal + text + + +`; + + const parsed = YAML.parse(input); + + const expected: any = "literal\n\ttext\n"; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/MJS9", () => { + // Spec Example 6.7. Block Folding + const input: string = `> + foo + + bar + + baz +`; + + const parsed = YAML.parse(input); + + const expected: any = "foo \n\n\t bar\n\nbaz\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/MUS6/00", () => { + // Directive variants + // Error test - expecting parse to fail + const input: string = `%YAML 1.1#... +--- +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/MUS6/01", () => { + // Directive variants + // Error test - expecting parse to fail + const input: string = `%YAML 1.2 +--- +%YAML 1.2 +--- +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/MUS6/02", () => { + // Directive variants + const input: string = `%YAML 1.1 +--- +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/MUS6/03", () => { + // Directive variants + const input: string = `%YAML 1.1 +--- +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/MUS6/04", () => { + // Directive variants + const input: string = `%YAML 1.1 # comment +--- +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/MUS6/05", () => { + // Directive variants + const input: string = `%YAM 1.1 +--- +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/MUS6/06", () => { + // Directive variants + const input: string = `%YAMLL 1.1 +--- +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/MXS3", () => { + // Flow Mapping in Block Sequence + const input: string = `- {a: b} +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ a: "b" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/MYW6", () => { + // Block Scalar Strip + const input: string = `|- + ab + + +... +`; + + const parsed = YAML.parse(input); + + const expected: any = "ab"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/MZX3", () => { + // Non-Specific Tags on Scalars + const input: string = `- plain +- "double quoted" +- 'single quoted' +- > + block +- plain again +`; + + const parsed = YAML.parse(input); + + const expected: any = ["plain", "double quoted", "single quoted", "block\n", "plain again"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/N4JP", () => { + // Bad indentation in mapping + // Error test - expecting parse to fail + const input: string = `map: + key1: "quoted1" + key2: "bad indentation" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/N782", () => { + // Invalid document markers in flow style + // Error test - expecting parse to fail + const input: string = `[ +--- , +... +] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/NAT4", () => { + // Various empty or newline only quoted strings + const input: string = `--- +a: ' + ' +b: ' + ' +c: " + " +d: " + " +e: ' + + ' +f: " + + " +g: ' + + + ' +h: " + + + " +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: " ", + b: " ", + c: " ", + d: " ", + e: "\n", + f: "\n", + g: "\n\n", + h: "\n\n", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/NB6Z", () => { + // Multiline plain value with tabs on empty lines + const input: string = `key: + value + with + + tabs +`; + + const parsed = YAML.parse(input); + + const expected: any = { key: "value with\ntabs" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/NHX8", () => { + // Empty Lines at End of Document (using test.event for expected values) + const input: string = `: + + +`; + + const parsed = YAML.parse(input); + + const expected: any = { null: null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/NJ66", () => { + // Multiline plain flow mapping key + const input: string = `--- +- { single line: value} +- { multi + line: value} +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ "single line": "value" }, { "multi line": "value" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/NKF9", () => { + // Empty keys in block and flow mapping (using test.event for expected values) + const input: string = `--- +key: value +: empty key +--- +{ + key: value, : empty key +} +--- +# empty key and value +: +--- +# empty key and value +{ : } +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { key: "value", null: "empty key" }, + { key: "value", null: "empty key" }, + { null: null }, + { null: null }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/NP9H", () => { + // Spec Example 7.5. Double Quoted Line Breaks + const input: string = `"folded +to a space, + +to a line feed, or \\ + \\ non-content" +`; + + const parsed = YAML.parse(input); + + const expected: any = "folded to a space,\nto a line feed, or \t \tnon-content"; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/P2AD", () => { + // Spec Example 8.1. Block Scalar Header + const input: string = `- | # Empty header↓ + literal +- >1 # Indentation indicator↓ + folded +- |+ # Chomping indicator↓ + keep + +- >1- # Both indicators↓ + strip +`; + + const parsed = YAML.parse(input); + + const expected: any = ["literal\n", " folded\n", "keep\n\n", " strip"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/P2EQ", () => { + // Invalid sequene item on same line as previous item + // Error test - expecting parse to fail + const input: string = `--- +- { y: z }- invalid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/P76L", () => { + // Spec Example 6.19. Secondary Tag Handle + const input: string = `%TAG !! tag:example.com,2000:app/ +--- +!!int 1 - 3 # Interval, not integer +`; + + const parsed = YAML.parse(input); + + const expected: any = "1 - 3"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/P94K", () => { + // Spec Example 6.11. Multi-Line Comments + const input: string = `key: # Comment + # lines + value + + +`; + + const parsed = YAML.parse(input); + + const expected: any = { key: "value" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/PBJ2", () => { + // Spec Example 2.3. Mapping Scalars to Sequences + const input: string = `american: + - Boston Red Sox + - Detroit Tigers + - New York Yankees +national: + - New York Mets + - Chicago Cubs + - Atlanta Braves +`; + + const parsed = YAML.parse(input); + + const expected: any = { + american: ["Boston Red Sox", "Detroit Tigers", "New York Yankees"], + national: ["New York Mets", "Chicago Cubs", "Atlanta Braves"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/PRH3", () => { + // Spec Example 7.9. Single Quoted Lines + const input: string = `' 1st non-empty + + 2nd non-empty + 3rd non-empty ' +`; + + const parsed = YAML.parse(input); + + const expected: any = " 1st non-empty\n2nd non-empty 3rd non-empty "; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/PUW8", () => { + // Document start on last line + const input: string = `--- +a: b +--- +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ a: "b" }, null]; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/PW8X", () => { + // Anchors on Empty Scalars (using test.event for expected values) + const input: string = `- &a +- a +- + &a : a + b: &b +- + &c : &a +- + ? &d +- + ? &e + : &a +`; + + const parsed = YAML.parse(input); + + const expected: any = [null, "a", { null: "a", b: null }, { null: null }, { null: null }, { null: null }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/Q4CL", () => { + // Trailing content after quoted value + // Error test - expecting parse to fail + const input: string = `key1: "quoted1" +key2: "quoted2" trailing content +key3: "quoted3" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/Q5MG", () => { + // Tab at beginning of line followed by a flow mapping + const input: string = ` {} +`; + + const parsed = YAML.parse(input); + + const expected: any = {}; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/Q88A", () => { + // Spec Example 7.23. Flow Content + const input: string = `- [ a, b ] +- { a: b } +- "a" +- 'b' +- c +`; + + const parsed = YAML.parse(input); + + const expected: any = [["a", "b"], { a: "b" }, "a", "b", "c"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/Q8AD", () => { + // Spec Example 7.5. Double Quoted Line Breaks [1.3] + const input: string = `--- +"folded +to a space, + +to a line feed, or \\ + \\ non-content" +`; + + const parsed = YAML.parse(input); + + const expected: any = "folded to a space,\nto a line feed, or \t \tnon-content"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/Q9WF", () => { + // Spec Example 6.12. Separation Spaces (using test.event for expected values) + const input: string = `{ first: Sammy, last: Sosa }: +# Statistics: + hr: # Home runs + 65 + avg: # Average + 0.278 +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "[object Object]": { hr: 65, avg: 0.278 }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/QB6E", () => { + // Wrong indented multiline quoted scalar + // Error test - expecting parse to fail + const input: string = `--- +quoted: "a +b +c" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/QF4Y", () => { + // Spec Example 7.19. Single Pair Flow Mappings + const input: string = `[ +foo: bar +] +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ foo: "bar" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/QLJ7", () => { + // Tag shorthand used in documents but only defined in the first + // Error test - expecting parse to fail + const input: string = `%TAG !prefix! tag:example.com,2011: +--- !prefix!A +a: b +--- !prefix!B +c: d +--- !prefix!C +e: f +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/QT73", () => { + // Comment and document-end marker + const input: string = `# comment +... +`; + + const parsed = YAML.parse(input); + + const expected: any = null; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/R4YG", () => { + // Spec Example 8.2. Block Indentation Indicator + const input: string = `- | + detected +- > + + + # detected +- |1 + explicit +- > + + detected +`; + + const parsed = YAML.parse(input); + + const expected: any = ["detected\n", "\n\n# detected\n", " explicit\n", "\t\ndetected\n"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/R52L", () => { + // Nested flow mapping sequence and mappings + const input: string = `--- +{ top1: [item1, {key2: value2}, item3], top2: value2 } +`; + + const parsed = YAML.parse(input); + + const expected: any = { + top1: ["item1", { key2: "value2" }, "item3"], + top2: "value2", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/RHX7", () => { + // YAML directive without document end marker + // Error test - expecting parse to fail + const input: string = `--- +key: value +%YAML 1.2 +--- +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/RLU9", () => { + // Sequence Indent + const input: string = `foo: +- 42 +bar: + - 44 +`; + + const parsed = YAML.parse(input); + + const expected: any = { + foo: [42], + bar: [44], + }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/RR7F", () => { + // Mixed Block Mapping (implicit to explicit) + const input: string = `a: 4.2 +? d +: 23 +`; + + const parsed = YAML.parse(input); + + const expected: any = { d: 23, a: 4.2 }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/RTP8", () => { + // Spec Example 9.2. Document Markers + const input: string = `%YAML 1.2 +--- +Document +... # Suffix +`; + + const parsed = YAML.parse(input); + + const expected: any = "Document"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/RXY3", () => { + // Invalid document-end marker in single quoted string + // Error test - expecting parse to fail + const input: string = `--- +' +... +' +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/RZP5", () => { + // Various Trailing Comments [1.3] (using test.event for expected values) + const input: string = `a: "double + quotes" # lala +b: plain + value # lala +c : #lala + d +? # lala + - seq1 +: # lala + - #lala + seq2 +e: &node # lala + - x: y +block: > # lala + abcde +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: "double quotes", + b: "plain value", + c: "d", + seq1: ["seq2"], + e: [{ x: "y" }], + block: "abcde\n", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/RZT7", () => { + // Spec Example 2.28. Log File + const input: string = `--- +Time: 2001-11-23 15:01:42 -5 +User: ed +Warning: + This is an error message + for the log file +--- +Time: 2001-11-23 15:02:31 -5 +User: ed +Warning: + A slightly different error + message. +--- +Date: 2001-11-23 15:03:17 -5 +User: ed +Fatal: + Unknown variable "bar" +Stack: + - file: TopClass.py + line: 23 + code: | + x = MoreObject("345\\n") + - file: MoreClass.py + line: 58 + code: |- + foo = bar +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { Time: "2001-11-23 15:01:42 -5", User: "ed", Warning: "This is an error message for the log file" }, + { Time: "2001-11-23 15:02:31 -5", User: "ed", Warning: "A slightly different error message." }, + { + Date: "2001-11-23 15:03:17 -5", + User: "ed", + Fatal: 'Unknown variable "bar"', + Stack: [ + { file: "TopClass.py", line: 23, code: 'x = MoreObject("345\\n")\n' }, + { file: "MoreClass.py", line: 58, code: "foo = bar" }, + ], + }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/S3PD", () => { + // Spec Example 8.18. Implicit Block Mapping Entries (using test.event for expected values) + const input: string = `plain key: in-line value +: # Both empty +"quoted key": +- entry +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "plain key": "in-line value", + null: null, + "quoted key": ["entry"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/S4GJ", () => { + // Invalid text after block scalar indicator + // Error test - expecting parse to fail + const input: string = `--- +folded: > first line + second line +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/S4JQ", () => { + // Spec Example 6.28. Non-Specific Tags + const input: string = `# Assuming conventional resolution: +- "12" +- 12 +- ! 12 +`; + + const parsed = YAML.parse(input); + + const expected: any = ["12", 12, "12"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/S4T7", () => { + // Document with footer + const input: string = `aaa: bbb +... +`; + + const parsed = YAML.parse(input); + + const expected: any = { aaa: "bbb" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/S7BG", () => { + // Colon followed by comma + const input: string = `--- +- :, +`; + + const parsed = YAML.parse(input); + + const expected: any = [":,"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/S98Z", () => { + // Block scalar with more spaces than first content line + // Error test - expecting parse to fail + const input: string = `empty block scalar: > + + + + # comment +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/S9E8", () => { + // Spec Example 5.3. Block Structure Indicators + const input: string = `sequence: +- one +- two +mapping: + ? sky + : blue + sea : green +`; + + const parsed = YAML.parse(input); + + const expected: any = { + sequence: ["one", "two"], + mapping: { sky: "blue", sea: "green" }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/SBG9", () => { + // Flow Sequence in Flow Mapping (using test.event for expected values) + const input: string = `{a: [b, c], [d, e]: f} +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: ["b", "c"], + "d,e": "f", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/SF5V", () => { + // Duplicate YAML directive + // Error test - expecting parse to fail + const input: string = `%YAML 1.2 +%YAML 1.2 +--- +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/SKE5", () => { + // Anchor before zero indented sequence + const input: string = `--- +seq: + &anchor +- a +- b +`; + + const parsed = YAML.parse(input); + + const expected: any = { + seq: ["a", "b"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/SM9W/00", () => { + // Single character streams + const input: string = "-"; + + const parsed = YAML.parse(input); + + const expected: any = [null]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/SM9W/01", () => { + // Single character streams (using test.event for expected values) + const input: string = ":"; + + const parsed = YAML.parse(input); + + const expected: any = { null: null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/SR86", () => { + // Anchor plus Alias + // Error test - expecting parse to fail + const input: string = `key1: &a value +key2: &b *a +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/SSW6", () => { + // Spec Example 7.7. Single Quoted Characters [1.3] + const input: string = `--- +'here''s to "quotes"' +`; + + const parsed = YAML.parse(input); + + const expected: any = 'here\'s to "quotes"'; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/SU5Z", () => { + // Comment without whitespace after doublequoted scalar + // Error test - expecting parse to fail + const input: string = `key: "value"# invalid comment +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/SU74", () => { + // Anchor and alias as mapping key + // Error test - expecting parse to fail + const input: string = `key1: &alias value1 +&b *alias : value2 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/SY6V", () => { + // Anchor before sequence entry on same line + // Error test - expecting parse to fail + const input: string = `&anchor - sequence entry +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/SYW4", () => { + // Spec Example 2.2. Mapping Scalars to Scalars + const input: string = `hr: 65 # Home runs +avg: 0.278 # Batting average +rbi: 147 # Runs Batted In +`; + + const parsed = YAML.parse(input); + + const expected: any = { hr: 65, avg: 0.278, rbi: 147 }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/T26H", () => { + // Spec Example 8.8. Literal Content [1.3] + const input: string = `--- | + + + literal + + + text + + # Comment +`; + + const parsed = YAML.parse(input); + + const expected: any = "\n\nliteral\n \n\ntext\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/T4YY", () => { + // Spec Example 7.9. Single Quoted Lines [1.3] + const input: string = `--- +' 1st non-empty + + 2nd non-empty + 3rd non-empty ' +`; + + const parsed = YAML.parse(input); + + const expected: any = " 1st non-empty\n2nd non-empty 3rd non-empty "; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/T5N4", () => { + // Spec Example 8.7. Literal Scalar [1.3] + const input: string = `--- | + literal + text + + +`; + + const parsed = YAML.parse(input); + + const expected: any = "literal\n\ttext\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/T833", () => { + // Flow mapping missing a separating comma + // Error test - expecting parse to fail + const input: string = `--- +{ + foo: 1 + bar: 2 } +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/TD5N", () => { + // Invalid scalar after sequence + // Error test - expecting parse to fail + const input: string = `- item1 +- item2 +invalid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/TE2A", () => { + // Spec Example 8.16. Block Mappings + const input: string = `block mapping: + key: value +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "block mapping": { key: "value" }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/TL85", () => { + // Spec Example 6.8. Flow Folding + const input: string = `" + foo + + bar + + baz +" +`; + + const parsed = YAML.parse(input); + + const expected: any = " foo\nbar\nbaz "; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/TS54", () => { + // Folded Block Scalar + const input: string = `> + ab + cd + + ef + + + gh +`; + + const parsed = YAML.parse(input); + + const expected: any = "ab cd\nef\n\ngh\n"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/U3C3", () => { + // Spec Example 6.16. ā€œTAGā€ directive + const input: string = `%TAG !yaml! tag:yaml.org,2002: +--- +!yaml!str "foo" +`; + + const parsed = YAML.parse(input); + + const expected: any = "foo"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/U3XV", () => { + // Node and Mapping Key Anchors + const input: string = `--- +top1: &node1 + &k1 key1: one +top2: &node2 # comment + key2: two +top3: + &k3 key3: three +top4: + &node4 + &k4 key4: four +top5: + &node5 + key5: five +top6: &val6 + six +top7: + &val7 seven +`; + + const parsed = YAML.parse(input); + + const expected: any = { + top1: { key1: "one" }, + top2: { key2: "two" }, + top3: { key3: "three" }, + top4: { key4: "four" }, + top5: { key5: "five" }, + top6: "six", + top7: "seven", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/U44R", () => { + // Bad indentation in mapping (2) + // Error test - expecting parse to fail + const input: string = `map: + key1: "quoted1" + key2: "bad indentation" +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/U99R", () => { + // Invalid comma in tag + // Error test - expecting parse to fail + const input: string = `- !!str, xxx +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/U9NS", () => { + // Spec Example 2.8. Play by Play Feed from a Game + const input: string = `--- +time: 20:03:20 +player: Sammy Sosa +action: strike (miss) +... +--- +time: 20:03:47 +player: Sammy Sosa +action: grand slam +... +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { time: "20:03:20", player: "Sammy Sosa", action: "strike (miss)" }, + { time: "20:03:47", player: "Sammy Sosa", action: "grand slam" }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/UDM2", () => { + // Plain URL in flow mapping + const input: string = `- { url: http://example.org } +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ url: "http://example.org" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/UDR7", () => { + // Spec Example 5.4. Flow Collection Indicators + const input: string = `sequence: [ one, two, ] +mapping: { sky: blue, sea: green } +`; + + const parsed = YAML.parse(input); + + const expected: any = { + sequence: ["one", "two"], + mapping: { sky: "blue", sea: "green" }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/UGM3", () => { + // Spec Example 2.27. Invoice + const input: string = `--- ! +invoice: 34843 +date : 2001-01-23 +bill-to: &id001 + given : Chris + family : Dumars + address: + lines: | + 458 Walkman Dr. + Suite #292 + city : Royal Oak + state : MI + postal : 48046 +ship-to: *id001 +product: + - sku : BL394D + quantity : 4 + description : Basketball + price : 450.00 + - sku : BL4438H + quantity : 1 + description : Super Hoop + price : 2392.00 +tax : 251.42 +total: 4443.52 +comments: + Late afternoon is best. + Backup contact is Nancy + Billsmer @ 338-4338. +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + const sharedAddress: any = { + given: "Chris", + family: "Dumars", + address: { + lines: "458 Walkman Dr.\nSuite #292\n", + city: "Royal Oak", + state: "MI", + postal: 48046, + }, + }; + const expected = { + "invoice": 34843, + "date": "2001-01-23", + "bill-to": sharedAddress, + "ship-to": sharedAddress, + "product": [ + { + sku: "BL394D", + quantity: 4, + description: "Basketball", + price: 450, + }, + { + sku: "BL4438H", + quantity: 1, + description: "Super Hoop", + price: 2392, + }, + ], + "tax": 251.42, + "total": 4443.52, + "comments": "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/UKK6/00", () => { + // Syntax character edge cases (using test.event for expected values) + const input: string = `- : +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ null: null }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/UKK6/01", () => { + // Syntax character edge cases + const input: string = `:: +`; + + const parsed = YAML.parse(input); + + const expected: any = { ":": null }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/UKK6/02", () => { + // Syntax character edge cases (using test.event for expected values) + const input: string = `! +`; + + const parsed = YAML.parse(input); + + const expected: any = ""; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/UT92", () => { + // Spec Example 9.4. Explicit Documents + const input: string = `--- +{ matches +% : 20 } +... +--- +# Empty +... +`; + + const parsed = YAML.parse(input); + + const expected: any = [{ "matches %": 20 }, null]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/UV7Q", () => { + // Legal tab after indentation + const input: string = `x: + - x + x +`; + + const parsed = YAML.parse(input); + + const expected: any = { + x: ["x x"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/V55R", () => { + // Aliases in Block Sequence + const input: string = `- &a a +- &b b +- *a +- *b +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + // Detected anchors that are referenced: a, b + + const expected: any = ["a", "b", "a", "b"]; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/V9D5", () => { + // Spec Example 8.19. Compact Block Mappings (using test.event for expected values) + const input: string = `- sun: yellow +- ? earth: blue + : moon: white +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + { sun: "yellow" }, + { + "[object Object]": { moon: "white" }, + }, + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/VJP3/00", () => { + // Flow collections over many lines + // Error test - expecting parse to fail + const input: string = `k: { +k +: +v +} +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/VJP3/01", () => { + // Flow collections over many lines + const input: string = `k: { + k + : + v + } +`; + + const parsed = YAML.parse(input); + + const expected: any = { + k: { k: "v" }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/W42U", () => { + // Spec Example 8.15. Block Sequence Entry Types + const input: string = `- # Empty +- | + block node +- - one # Compact + - two # sequence +- one: two # Compact mapping +`; + + const parsed = YAML.parse(input); + + const expected: any = [null, "block node\n", ["one", "two"], { one: "two" }]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/W4TN", () => { + // Spec Example 9.5. Directives Documents + const input: string = `%YAML 1.2 +--- | +%!PS-Adobe-2.0 +... +%YAML 1.2 +--- +# Empty +... +`; + + const parsed = YAML.parse(input); + + const expected: any = ["%!PS-Adobe-2.0\n", null]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/W5VH", () => { + // Allowed characters in alias + const input: string = `a: &:@*!$": scalar a +b: *:@*!$": +`; + + const parsed = YAML.parse(input); + + // This YAML has anchors and aliases - creating shared references + + const expected: any = { a: "scalar a", b: "scalar a" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/W9L4", () => { + // Literal block scalar with more spaces in first line + // Error test - expecting parse to fail + const input: string = `--- +block scalar: | + + more spaces at the beginning + are invalid +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/WZ62", () => { + // Spec Example 7.2. Empty Content + const input: string = `{ + foo : !!str, + !!str : bar, +} +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "", "": "bar" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/X38W", () => { + // Aliases in Flow Objects + // Special case: *a references the same array as first key, creating duplicate key + const input: string = `{ &a [a, &b b]: *b, *a : [c, *b, d]} +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "a,b": ["c", "b", "d"], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/X4QW", () => { + // Comment without whitespace after block scalar indicator + // Error test - expecting parse to fail + const input: string = `block: ># comment + scalar +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/X8DW", () => { + // Explicit key and value seperated by comment + const input: string = `--- +? key +# comment +: value +`; + + const parsed = YAML.parse(input); + + const expected: any = { key: "value" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/XLQ9", () => { + // Multiline scalar that looks like a YAML directive + const input: string = `--- +scalar +%YAML 1.2 +`; + + const parsed = YAML.parse(input); + + const expected: any = "scalar %YAML 1.2"; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/XV9V", () => { + // Spec Example 6.5. Empty Lines [1.3] + const input: string = `Folding: + "Empty line + + as a line feed" +Chomping: | + Clipped empty lines + + +`; + + const parsed = YAML.parse(input); + + const expected: any = { Folding: "Empty line\nas a line feed", Chomping: "Clipped empty lines\n" }; + + expect(parsed).toEqual(expected); +}); + +test.todo("yaml-test-suite/XW4D", () => { + // Various Trailing Comments (using test.event for expected values) + const input: string = `a: "double + quotes" # lala +b: plain + value # lala +c : #lala + d +? # lala + - seq1 +: # lala + - #lala + seq2 +e: + &node # lala + - x: y +block: > # lala + abcde +`; + + const parsed = YAML.parse(input); + + const expected: any = { + a: "double quotes", + b: "plain value", + c: "d", + seq1: ["seq2"], + e: [{ x: "y" }], + block: "abcde\n", + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/Y2GN", () => { + // Anchor with colon in the middle + const input: string = `--- +key: &an:chor value +`; + + const parsed = YAML.parse(input); + + const expected: any = { key: "value" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/Y79Y/000", () => { + // Tabs in various contexts + // Error test - expecting parse to fail + const input: string = `foo: | + +bar: 1 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/Y79Y/001", () => { + // Tabs in various contexts + const input: string = `foo: | + +bar: 1 +`; + + const parsed = YAML.parse(input); + + const expected: any = { foo: "\t\n", bar: 1 }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/Y79Y/002", () => { + // Tabs in various contexts + const input: string = `- [ + + foo + ] +`; + + const parsed = YAML.parse(input); + + const expected: any = [["foo"]]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/Y79Y/003", () => { + // Tabs in various contexts + // Error test - expecting parse to fail + const input: string = `- [ + foo, + foo + ] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/Y79Y/004", () => { + // Tabs in various contexts + // Error test - expecting parse to fail + const input: string = `- - +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/Y79Y/005", () => { + // Tabs in various contexts + // Error test - expecting parse to fail + const input: string = `- - +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/Y79Y/006", () => { + // Tabs in various contexts + // Error test - expecting parse to fail + const input: string = `? - +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/Y79Y/007", () => { + // Tabs in various contexts + // Error test - expecting parse to fail + const input: string = `? - +: - +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/Y79Y/008", () => { + // Tabs in various contexts + // Error test - expecting parse to fail + const input: string = `? key: +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/Y79Y/009", () => { + // Tabs in various contexts + // Error test - expecting parse to fail + const input: string = `? key: +: key: +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/Y79Y/010", () => { + // Tabs in various contexts + const input: string = `- -1 +`; + + const parsed = YAML.parse(input); + + const expected: any = [-1]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/YD5X", () => { + // Spec Example 2.5. Sequence of Sequences + const input: string = `- [name , hr, avg ] +- [Mark McGwire, 65, 0.278] +- [Sammy Sosa , 63, 0.288] +`; + + const parsed = YAML.parse(input); + + const expected: any = [ + ["name", "hr", "avg"], + ["Mark McGwire", 65, 0.278], + ["Sammy Sosa", 63, 0.288], + ]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/YJV2", () => { + // Dash in flow sequence + // Error test - expecting parse to fail + const input: string = `[-] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/Z67P", () => { + // Spec Example 8.21. Block Scalar Nodes [1.3] + const input: string = `literal: |2 + value +folded: !foo >1 + value +`; + + const parsed = YAML.parse(input); + + const expected: any = { literal: "value\n", folded: "value\n" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/Z9M4", () => { + // Spec Example 6.22. Global Tag Prefix + const input: string = `%TAG !e! tag:example.com,2000:app/ +--- +- !e!foo "bar" +`; + + const parsed = YAML.parse(input); + + const expected: any = ["bar"]; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/ZCZ6", () => { + // Invalid mapping in plain single line value + // Error test - expecting parse to fail + const input: string = `a: b: c: d +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/ZF4X", () => { + // Spec Example 2.6. Mapping of Mappings + const input: string = `Mark McGwire: {hr: 65, avg: 0.278} +Sammy Sosa: { + hr: 63, + avg: 0.288 + } +`; + + const parsed = YAML.parse(input); + + const expected: any = { + "Mark McGwire": { hr: 65, avg: 0.278 }, + "Sammy Sosa": { hr: 63, avg: 0.288 }, + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/ZH7C", () => { + // Anchors in Mapping + const input: string = `&a a: b +c: &d d +`; + + const parsed = YAML.parse(input); + + const expected: any = { a: "b", c: "d" }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/ZK9H", () => { + // Nested top level flow mapping + const input: string = `{ key: [[[ + value + ]]] +} +`; + + const parsed = YAML.parse(input); + + const expected: any = { + key: [[["value"]]], + }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/ZL4Z", () => { + // Invalid nested mapping + // Error test - expecting parse to fail + const input: string = `--- +a: 'b': c +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test("yaml-test-suite/ZVH3", () => { + // Wrong indented sequence item + // Error test - expecting parse to fail + const input: string = `- key: value + - item1 +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); + +test.todo("yaml-test-suite/ZWK4", () => { + // Key with anchor after missing explicit mapping value + const input: string = `--- +a: 1 +? b +&anchor c: 3 +`; + + const parsed = YAML.parse(input); + + const expected: any = { a: 1, b: null, c: 3 }; + + expect(parsed).toEqual(expected); +}); + +test("yaml-test-suite/ZXT5", () => { + // Implicit key followed by newline and adjacent value + // Error test - expecting parse to fail + const input: string = `[ "key" + :value ] +`; + + expect(() => { + return YAML.parse(input); + }).toThrow(); +}); diff --git a/test/js/bun/yaml/yaml.test.ts b/test/js/bun/yaml/yaml.test.ts index df904d64e9..f3e0d949ba 100644 --- a/test/js/bun/yaml/yaml.test.ts +++ b/test/js/bun/yaml/yaml.test.ts @@ -1,5 +1,6 @@ -import { YAML } from "bun"; +import { YAML, file } from "bun"; import { describe, expect, test } from "bun:test"; +import { join } from "path"; describe("Bun.YAML", () => { describe("parse", () => { @@ -702,6 +703,64 @@ production: }, }); }); + + test("issue 22659", () => { + const input1 = `- test2: next + test1: +`; + expect(YAML.parse(input1)).toMatchInlineSnapshot(` + [ + { + "test1": "+", + "test2": "next", + }, + ] + `); + const input2 = `- test1: + + test2: next`; + expect(YAML.parse(input2)).toMatchInlineSnapshot(` + [ + { + "test1": "+", + "test2": "next", + }, + ] + `); + }); + + test("issue 22392", () => { + const input = ` +foo: "some + ... + string" +`; + expect(YAML.parse(input)).toMatchInlineSnapshot(` + { + "foo": "some ... string", + } + `); + }); + + test("issue 22286", async () => { + const input1 = ` +my_anchor: &MyAnchor "MyAnchor" + +my_config: + *MyAnchor : + some_key: "some_value" +`; + expect(YAML.parse(input1)).toMatchInlineSnapshot(` + { + "my_anchor": "MyAnchor", + "my_config": { + "MyAnchor": { + "some_key": "some_value", + }, + }, + } + `); + const input2 = await file(join(import.meta.dir, "fixtures", "AHatInTime.yaml")).text(); + expect(YAML.parse(input2)).toMatchSnapshot(); + }); }); describe("stringify", () => {