From ca16b99651c69b263519508e420c43a6e03e2fd3 Mon Sep 17 00:00:00 2001 From: Adam Malcontenti-Wilson Date: Thu, 1 Aug 2024 11:56:02 +1000 Subject: [PATCH] Add inline comment support --- README.md | 6 +++ lib/psych/comments/emitter.rb | 12 ++++- lib/psych/comments/node_ext.rb | 2 + lib/psych/comments/parsing.rb | 11 ++++- spec/emitter/example2-out.yml | 3 +- spec/emitter/example5-out.yml | 3 +- spec/parsing_spec.rb | 89 +++++++++++++++++++++++++++++----- 7 files changed, 107 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 8dac5cc..30f1fc1 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,12 @@ Returns an array of leading comments. Each comment must start with `#`. Extends [Psych::Nodes::Node](https://docs.ruby-lang.org/en/3.1/Psych/Nodes/Node.html). +### `Psych::Nodes::Node#inline_comment` -> `String | nil` + +Returns the inline comment. Each comment must start with `#`. + +Extends [Psych::Nodes::Node](https://docs.ruby-lang.org/en/3.1/Psych/Nodes/Node.html). + ### `Psych::Nodes::Node#trailing_comments` -> `Array` Returns an array of leading comments. Each comment must start with `#`. diff --git a/lib/psych/comments/emitter.rb b/lib/psych/comments/emitter.rb index ac605b8..c0eed05 100644 --- a/lib/psych/comments/emitter.rb +++ b/lib/psych/comments/emitter.rb @@ -131,6 +131,10 @@ def emit(node) else print stringify_adjust_scalar(node, INDENT * @indent) end + if node.inline_comment + space! + emit_comment(node.inline_comment) + end when Psych::Nodes::Mapping set_flow(flow?(node)) do if @flow @@ -161,6 +165,7 @@ def emit(node) emit(value) end end + emit_comment(node.inline_comment, newline: false) if node.inline_comment newline! end end @@ -192,6 +197,7 @@ def emit(node) emit(subnode) end end + emit_comment(node.inline_comment, newline: false) if node.inline_comment newline! end end @@ -236,12 +242,12 @@ def emit_lookahead_comments(node) @comment_lookahead.push(node) end - def emit_comment(comment) + def emit_comment(comment, newline: true) unless /\A#[^\r\n]*\z/.match?(comment) raise ArgumentError, "Invalid comment: #{comment.inspect}" end print comment - newline! + newline! if newline end def indented(&block) @@ -267,6 +273,8 @@ def single_line?(node) end def flow?(node) + return false if node.inline_comment # inline comments cannot be in flow + case node when Psych::Nodes::Scalar, Psych::Nodes::Alias true diff --git a/lib/psych/comments/node_ext.rb b/lib/psych/comments/node_ext.rb index e79ccd2..75ba585 100644 --- a/lib/psych/comments/node_ext.rb +++ b/lib/psych/comments/node_ext.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Psych::Nodes::Node + attr_accessor :inline_comment + def leading_comments @leading_comments ||= [] end diff --git a/lib/psych/comments/parsing.rb b/lib/psych/comments/parsing.rb index 09d4609..f6e763a 100644 --- a/lib/psych/comments/parsing.rb +++ b/lib/psych/comments/parsing.rb @@ -19,6 +19,10 @@ def sublines(sl, sc, el, ec) end end + def line_remaining(l, sc) + sublines(l, sc, l, nil) + end + def char_at(l, c) (@lines[l] || "")[c] end @@ -46,7 +50,10 @@ def visit(node) case node when Psych::Nodes::Scalar, Psych::Nodes::Alias node.leading_comments.push(*read_comments(node.start_line, node.start_column)) - @last = [node.end_line, node.end_column] + has_delim = char_at(node.end_line, node.end_column) == ":" + inline_comment = line_remaining(node.end_line, node.end_column + (has_delim ? 1 : 0)).match(/^\s*(#.*)?$/) + node.inline_comment = inline_comment[1] if inline_comment && inline_comment[1] + @last = [node.end_line, node.end_column + (inline_comment&.values_at(0)&.first&.length || 0)] when Psych::Nodes::Sequence, Psych::Nodes::Mapping has_delim = /[\[{]/.match?(char_at(node.start_line, node.start_column)) has_bullet = node.is_a?(Psych::Nodes::Sequence) && !has_delim @@ -58,6 +65,8 @@ def visit(node) end if has_delim target = node.children[-1] || node + inline_comment = line_remaining(node.end_line, node.end_column).match(/^\s*(#.*)?$/) + node.inline_comment = inline_comment[1] if inline_comment && inline_comment[1] target.trailing_comments.push(*read_comments(node.end_line, node.end_column)) end when Psych::Nodes::Document diff --git a/spec/emitter/example2-out.yml b/spec/emitter/example2-out.yml index 577a8dd..aeb8907 100644 --- a/spec/emitter/example2-out.yml +++ b/spec/emitter/example2-out.yml @@ -1,6 +1,5 @@ # foo -- foo -# foo2 +- foo # foo2 # foo - foo - foo: bar diff --git a/spec/emitter/example5-out.yml b/spec/emitter/example5-out.yml index f11783b..22595f6 100644 --- a/spec/emitter/example5-out.yml +++ b/spec/emitter/example5-out.yml @@ -3,8 +3,7 @@ foo: bar: baz bar2: # test - foo: foo - #testtest + foo: foo #testtest # test bar: bar bar3: diff --git a/spec/parsing_spec.rb b/spec/parsing_spec.rb index b56ff4f..3c5ece7 100644 --- a/spec/parsing_spec.rb +++ b/spec/parsing_spec.rb @@ -21,11 +21,22 @@ end it "attaches comments to a scalar" do - ast = Psych::Comments.parse("# foo\nbar" + <<~YAML) + ast = Psych::Comments.parse(<<~YAML) # foo bar YAML expect(ast.root.leading_comments).to eq(["# foo"]) + expect(ast.root.inline_comment).to eq(nil) + expect(ast.root.trailing_comments).to eq([]) + end + + it "attaches inline comments to a scalar" do + ast = Psych::Comments.parse(<<~YAML) + bar # foo + YAML + expect(ast.root.leading_comments).to eq([]) + expect(ast.root.inline_comment).to eq("# foo") + expect(ast.root.trailing_comments).to eq([]) end it "attaches multiple comments to a scalar" do @@ -37,7 +48,7 @@ expect(ast.root.leading_comments).to eq(["# foo", "# bar"]) end - it "attaches comments to a mapping key" do + it "attaches leading comments to a mapping key" do ast = Psych::Comments.parse(<<~YAML) # foo bar: baz @@ -46,7 +57,26 @@ expect(ast.root.children[0].leading_comments).to eq(["# foo"]) end - it "attaches comments to a mapping value" do + it "attaches inline comments to a mapping key" do + ast = Psych::Comments.parse(<<~YAML) + bar: # foo + baz + YAML + expect(ast.root).to be_a(Psych::Nodes::Mapping) + expect(ast.root.children[0].inline_comment).to eq("# foo") + expect(ast.root.children[1].inline_comment).to eq(nil) + end + + it "attaches inline comments to a mapping value" do + ast = Psych::Comments.parse(<<~YAML) + bar: baz # foo + YAML + expect(ast.root).to be_a(Psych::Nodes::Mapping) + expect(ast.root.children[0].inline_comment).to eq(nil) + expect(ast.root.children[1].inline_comment).to eq("# foo") + end + + it "attaches leading comments to a mapping value" do ast = Psych::Comments.parse(<<~YAML) bar: # foo @@ -64,24 +94,57 @@ bar2 # baz-a - # baz-b - foo3: bar3 + foo3: bar3 # baz-c YAML expect(ast.root).to be_a(Psych::Nodes::Sequence) expect(ast.root.children[0].leading_comments).to eq(["# foo"]) expect(ast.root.children[1].leading_comments).to eq(["# bar"]) expect(ast.root.children[2].leading_comments).to eq(["# baz-a"]) + expect(ast.root.children[2].children[1].inline_comment).to eq("# baz-c") + end + + it "attaches inline comments to empty flow mapping/sequence" do + ast = Psych::Comments.parse(<<~YAML) + foo: {} # foo + bar: [] # bar + YAML + + expect(ast.root).to be_a(Psych::Nodes::Mapping) + expect(ast.root.children[1].inline_comment).to eq("# foo") + expect(ast.root.children[3].inline_comment).to eq("# bar") + end + + it "attaches inline comments only to flow mapping/sequence" do + ast = Psych::Comments.parse(<<~YAML) + foo: { key: "name", values: ["a", "b", "c"] } # foo + bar: [{ key: "name", values: ["a", "b", "c"]}] # bar + baz: + - { key: "name", values: ["a", "b", "c"] } # baz + YAML + expected_comments = { + ast.root.children[1] => "# foo", + ast.root.children[3] => "# bar", + ast.root.children[5].children[0] => "# baz" + } + visit = lambda do |node| + expect(node.inline_comment).to eq(expected_comments[node]) + node.children&.each { |child| visit.call(child) } + end + visit.call(ast.root) end it "attaches comments to flow mapping" do ast = Psych::Comments.parse(<<~YAML) - # foo + # leading { - foo: bar + foo: bar # foo # bar - } + } # inline YAML expect(ast.root).to be_a(Psych::Nodes::Mapping) - expect(ast.root.leading_comments).to eq(["# foo"]) + expect(ast.root.leading_comments).to eq(["# leading"]) + expect(ast.root.inline_comment).to eq("# inline") + expect(ast.root.children[1].inline_comment).to eq("# foo") expect(ast.root.children[1].trailing_comments).to eq(["# bar"]) end @@ -89,13 +152,15 @@ ast = Psych::Comments.parse(<<~YAML) # foo [ - foo - # bar - ] + foo # bar + # baz + ] # bazza YAML expect(ast.root).to be_a(Psych::Nodes::Sequence) expect(ast.root.leading_comments).to eq(["# foo"]) - expect(ast.root.children[0].trailing_comments).to eq(["# bar"]) + expect(ast.root.inline_comment).to eq("# bazza") + expect(ast.root.children[0].inline_comment).to eq("# bar") + expect(ast.root.children[0].trailing_comments).to eq(["# baz"]) end it "attaches comments to document" do