diff --git a/lib/jsduck/doc_formatter.rb b/lib/jsduck/doc_formatter.rb index 240b986aa5d876adb07ea73b96f37bd941a3c3f1..7718f87647d3b1130c8cea5a20b07edb60353894 100644 --- a/lib/jsduck/doc_formatter.rb +++ b/lib/jsduck/doc_formatter.rb @@ -1,6 +1,7 @@ require 'rubygems' require 'strscan' require 'rdiscount' +require 'jsduck/logger' require 'jsduck/inline/link' require 'jsduck/inline/img' require 'jsduck/inline/video' @@ -19,6 +20,7 @@ module JsDuck @inline_img = Inline::Img.new(opts) @inline_video = Inline::Video.new(opts) @inline_example = Inline::Example.new(opts) + @doc_context = {} end # Sets base path to prefix images from {@img} tags. @@ -41,13 +43,14 @@ module JsDuck # Sets up instance to work in context of particular doc object. # Used for error reporting. def doc_context=(doc) + @doc_context = doc @inline_video.doc_context = doc @inline_link.doc_context = doc end # Returns the current documentation context def doc_context - @inline_link.doc_context + @doc_context end # JsDuck::Relations for looking up class names. @@ -94,10 +97,10 @@ module JsDuck s = StringScanner.new(input) out = "" - # Keep track of the nesting level of tags. We're not - # auto-detecting class names when inside . Normally links - # shouldn't be nested, but just to be extra safe. - open_a_tags = 0 + # Keep track of open HTML tags. We're not auto-detecting class + # names when inside . Also we want to close down the unclosed + # tags. + open_tags = [] while !s.eos? do if substitute = @inline_link.replace(s) @@ -111,28 +114,63 @@ module JsDuck out += s.scan(/[{]/) elsif substitute = @inline_example.replace(s) out += substitute - elsif s.check(/ tags. - open_a_tags += 1 + elsif s.check(/<\w/) + # Push the tag to open_tags stack + out += s.scan(/|\Z/) - elsif s.check(/<\/a>/) - # closed, auto-detection may continue when no more tags open. - open_a_tags -= 1 - out += s.scan(/<\/a>/) + elsif s.check(/<\/\w+>/) + # Close the tag + out += s.scan(/<\//) + tag = s.scan(/\w+/) + if open_tags.include?(tag) + # delete the most recent unclosed tag in our tags stack + open_tags.delete_at(open_tags.index(tag)) + end + out += tag + out += s.scan(/>/) elsif s.check(/|\Z/) + # Ignore plain '<' char. + out += s.scan(/... text = s.scan(/[^{<]+/) - out += open_a_tags > 0 ? text : @inline_link.create_magic_links(text) + out += open_tags.include?("a") ? text : @inline_link.create_magic_links(text) end end + if open_tags.length > 0 + # Print warnings for unclosed tags + ctx = @doc_context + tag_list = open_tags.map {|tag| "<#{tag}>" }.join(", ") + Logger.warn(:html, "Unclosed HTML tag: #{tag_list}", ctx[:filename], ctx[:linenr]) + + # Automatically close all unclosed tags + out += open_tags.map {|tag| "" }.join + end + out end + # Tags that don't require closing + VOID_TAGS = { + "base" => true, + "link" => true, + "meta" => true, + "hr" => true, + "br" => true, + "wbr" => true, + "area" => true, + "img" => true, + "param" => true, + "input" => true, + "isindex" => true, + "option" => true, + } + # Creates a link based on the link template. def link(cls, member, anchor_text, type=nil, static=nil) @inline_link.link(cls, member, anchor_text, type, static) diff --git a/lib/jsduck/logger.rb b/lib/jsduck/logger.rb index c745b29a4749f4f53bb1d62290f21f722d5d68dc..cc76009e8d76f4b40d258fc05e3d46949c568b68 100644 --- a/lib/jsduck/logger.rb +++ b/lib/jsduck/logger.rb @@ -20,6 +20,7 @@ module JsDuck [:link, "{@link} to unknown class or member"], [:link_ambiguous, "{@link} is ambiguous"], [:link_auto, "Auto-detected link to unknown class or member"], + [:html, "Unclosed HTML tag."], [:alt_name, "Name used as both classname and alternate classname"], [:name_missing, "Member or parameter has no name"], diff --git a/spec/doc_formatter_spec.rb b/spec/doc_formatter_spec.rb index 3feab1ec8a2e2f81a41c5cc3364122a6e5674359..6b9a4bd8fa9ff121b2df1a267b039b90ed90d051 100644 --- a/spec/doc_formatter_spec.rb +++ b/spec/doc_formatter_spec.rb @@ -367,6 +367,18 @@ describe JsDuck::DocFormatter do @formatter.format("Hello **world**").should =~ /Hello world<\/strong>/ end + it "closes unclosed tags" do + @formatter.format("Hello").should =~ /Hello.*<\/b>/m + end + + it "closes unclosed and in correct order" do + @formatter.format("Hello").should =~ /<\/b><\/a>/ + end + + it "doesn't close unclosed tags" do + @formatter.format("").should_not =~ /<\/img>/ + end + shared_examples_for "code blocks" do it "contains text before" do @html.should =~ /Some code/