diff --git a/spec/compiler/semantic/doc_spec.cr b/spec/compiler/semantic/doc_spec.cr index 54f3f60e4949..088c53cb5bfc 100644 --- a/spec/compiler/semantic/doc_spec.cr +++ b/spec/compiler/semantic/doc_spec.cr @@ -62,35 +62,117 @@ describe "Semantic: doc" do bar.doc.should eq("Hello") end - it "stores doc for const when using ditto" do - result = semantic %( - # A number - ONE = 1 + describe ":ditto:" do + it "stores doc for const" do + result = semantic %( + # A number + ONE = 1 - # :ditto: - TWO = 2 - ), wants_doc: true - program = result.program - program.types["ONE"].doc.should eq "A number" - program.types["TWO"].doc.should eq "A number" - end + # :ditto: + TWO = 2 + ), wants_doc: true + program = result.program + program.types["ONE"].doc.should eq "A number" + program.types["TWO"].doc.should eq "A number" + end - it "stores doc for def when using ditto" do - result = semantic %( - class Foo + it "stores doc for def" do + result = semantic %( + class Foo + # Hello + def bar + end + + # :ditto: + def bar2 + end + end + ), wants_doc: true + program = result.program + foo = program.types["Foo"] + bar = foo.lookup_defs("bar2").first + bar.doc.should eq("Hello") + end + + it "stores doc for macro" do + result = semantic %( # Hello - def bar + macro bar end # :ditto: - def bar2 + macro bar2 end - end - ), wants_doc: true - program = result.program - foo = program.types["Foo"] - bar = foo.lookup_defs("bar2").first - bar.doc.should eq("Hello") + ), wants_doc: true + program = result.program + bar2 = program.lookup_macros("bar2").as(Array(Macro)).first + bar2.doc.should eq("Hello") + end + + it "amend previous doc" do + result = semantic %( + class Foo + # Hello + def bar + end + + # :ditto: + # + # World + def bar2 + end + end + ), wants_doc: true + program = result.program + foo = program.types["Foo"] + bar = foo.lookup_defs("bar2").first + bar.doc.should eq("Hello\n\nWorld") + end + + it "amend previous doc (without empty line)" do + result = semantic %( + class Foo + # Hello + def bar + end + + # :ditto: + # World + def bar2 + end + end + ), wants_doc: true + program = result.program + foo = program.types["Foo"] + bar = foo.lookup_defs("bar2").first + bar.doc.should eq("Hello\n\nWorld") + end + + it ":ditto: references last non-ditto doc" do + result = semantic %( + class Foo + # Hello + def bar + end + + # :ditto: + # + # World + def bar2 + end + + # :ditto: + # + # Crystal + def bar3 + end + end + ), wants_doc: true + program = result.program + foo = program.types["Foo"] + bar = foo.lookup_defs("bar3").first + bar.doc.should eq("Hello\n\nCrystal") + end end it "stores doc for def with visibility" do @@ -148,21 +230,6 @@ describe "Semantic: doc" do bar.doc.should eq("Hello") end - it "stores doc for macro when using ditto" do - result = semantic %( - # Hello - macro bar - end - - # :ditto: - macro bar2 - end - ), wants_doc: true - program = result.program - bar2 = program.lookup_macros("bar2").as(Array(Macro)).first - bar2.doc.should eq("Hello") - end - {% for def_type in %w[def macro].map &.id %} it "overwrites doc for {{def_type}} when redefining" do result = semantic %( diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 62ee7a8b260c..a57258f67df2 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -1090,12 +1090,18 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor def check_ditto(node : Def | Assign | FunDef | Const | Macro, location : Location?) : Nil return if !@program.wants_doc? - stripped_doc = node.doc.try &.strip - if stripped_doc == ":ditto:" - node.doc = @last_doc - else - @last_doc = node.doc + + if stripped_doc = node.doc.try &.strip + if stripped_doc == ":ditto:" + node.doc = @last_doc + return + elsif appendix = stripped_doc.lchop?(":ditto:\n") + node.doc = "#{@last_doc}\n\n#{appendix.lchop('\n')}" + return + end end + + @last_doc = node.doc end def annotations_doc(annotations)