lib/rubocop/markdown/preprocess.rb in rubocop-md-1.2.1 vs lib/rubocop/markdown/preprocess.rb in rubocop-md-1.2.2

- old
+ new

@@ -5,16 +5,22 @@ module RuboCop module Markdown # Transform source Markdown file into valid Ruby file # by commenting out all non-code lines class Preprocess - # This is a regexp to extract code blocks from .md files. + # This is a regexp to parse code blocks from .md files. # # Only recognizes backticks-style code blocks. # # Try it: https://rubular.com/r/YMqSWiBuh2TKIJ - MD_REGEXP = /^([ \t]*`{3,4})([\w[[:blank:]]+]*\n)([\s\S]+?)(^[ \t]*\1[[:blank:]]*\n?)/m.freeze + MD_REGEXP = / + ^([[:blank:]]*`{3,4}) # Match opening backticks + ([\w[[:blank:]]+]*)?\n # Match the code block syntax + ([\s\S]+?) # Match everything inside the code block + (^[[:blank:]]*\1[[:blank:]]*\n?) # Match closing backticks + |(^.*$) # If we are not in a codeblock, match the whole line + /x.freeze MARKER = "<--rubocop/md-->" # See https://github.com/github/linguist/blob/v5.3.3/lib/linguist/languages.yml#L3925 RUBY_TYPES = %w[ @@ -24,30 +30,10 @@ rake rb rbx ].freeze - class Walker # :nodoc: - STEPS = %i[text code_start code_attr code_body code_end].freeze - - STEPS.each do |step| - define_method("#{step}?") do - STEPS[current_step] == step - end - end - - attr_accessor :current_step - - def initialize - @current_step = 0 - end - - def next! - self.current_step = current_step == (STEPS.size - 1) ? 0 : current_step + 1 - end - end - class << self # Revert preprocess changes. # # When autocorrect is applied, RuboCop re-writes the file # using preproccessed source buffer. @@ -66,35 +52,38 @@ @config = Markdown.config_store.for(file) end # rubocop:disable Metrics/MethodLength def call(src) - parts = src.split(MD_REGEXP) + src.gsub(MD_REGEXP) do |full_match| + m = Regexp.last_match + open_backticks = m[1] + syntax = m[2] + code = m[3] + close_backticks = m[4] + markdown = m[5] - walker = Walker.new - - parts.each do |part| - if walker.code_body? && maybe_ruby?(@syntax) && valid_syntax?(@syntax, part) - next walker.next! + if markdown + # We got markdown outside of a codeblock + comment_lines(markdown) + elsif ruby_codeblock?(syntax, code) + # The codeblock we parsed is assumed ruby, keep as is and append markers to backticks + "#{comment_lines(open_backticks + syntax)}\n#{code}#{comment_lines(close_backticks)}" + else + # The codeblock is not relevant, comment it out + comment_lines(full_match) end - - if walker.code_attr? - @syntax = part.gsub(/(^\s+|\s+$)/, "") - next walker.next! - end - - comment_lines! part - - walker.next! end - - parts.join end # rubocop:enable Metrics/MethodLength private + def ruby_codeblock?(syntax, src) + maybe_ruby?(syntax) && valid_syntax?(syntax, src) + end + # Check codeblock attribute to prevent from parsing # non-Ruby snippets and avoid false positives def maybe_ruby?(syntax) (autodetect? && syntax.empty?) || ruby?(syntax) end @@ -122,13 +111,11 @@ # If it's set to false we lint only implicitly specified Ruby blocks. def autodetect? config["Markdown"]&.fetch("Autodetect", true) end - def comment_lines!(src) - return if src =~ /\A\n\z/ - - src.gsub!(/^(.)/m, "##{MARKER}\\1") + def comment_lines(src) + src.gsub(/^/, "##{MARKER}") end end end end