rubocop-prose 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/AGENTS.md +13 -0
- data/CHANGELOG.md +82 -0
- data/CONTRIBUTING.md +77 -0
- data/DESIGN.md +51 -0
- data/LICENSE.txt +21 -0
- data/README.md +152 -0
- data/RELEASING.md +32 -0
- data/assets/README.md +3 -0
- data/assets/RUBOCOP-LICENSE +20 -0
- data/assets/logo.gif +0 -0
- data/assets/logo.png +0 -0
- data/assets/logo.svg +80 -0
- data/assets/rubocop-logo.svg +1 -0
- data/config/cops/markdown/atx_heading_space.yml +3 -0
- data/config/cops/markdown/atx_heading_spacing.yml +3 -0
- data/config/cops/markdown/bare_url.yml +7 -0
- data/config/cops/markdown/blockquote_blank_lines.yml +3 -0
- data/config/cops/markdown/blockquote_spacing.yml +3 -0
- data/config/cops/markdown/closed_heading_space.yml +3 -0
- data/config/cops/markdown/closed_heading_spacing.yml +3 -0
- data/config/cops/markdown/code_block_style.yml +8 -0
- data/config/cops/markdown/code_span_spacing.yml +3 -0
- data/config/cops/markdown/compact_lists.yml +3 -0
- data/config/cops/markdown/descriptive_link_text.yml +8 -0
- data/config/cops/markdown/duplicate_headings.yml +4 -0
- data/config/cops/markdown/emphasis_heading.yml +3 -0
- data/config/cops/markdown/emphasis_spacing.yml +3 -0
- data/config/cops/markdown/emphasis_style.yml +4 -0
- data/config/cops/markdown/empty_link.yml +3 -0
- data/config/cops/markdown/fence_blank_lines.yml +6 -0
- data/config/cops/markdown/fence_language.yml +4 -0
- data/config/cops/markdown/fence_style.yml +8 -0
- data/config/cops/markdown/first_heading.yml +6 -0
- data/config/cops/markdown/front_matter.yml +2 -0
- data/config/cops/markdown/hard_break_style.yml +4 -0
- data/config/cops/markdown/hard_tabs.yml +5 -0
- data/config/cops/markdown/heading_blank_lines.yml +5 -0
- data/config/cops/markdown/heading_increment.yml +3 -0
- data/config/cops/markdown/heading_indentation.yml +3 -0
- data/config/cops/markdown/heading_punctuation.yml +4 -0
- data/config/cops/markdown/heading_structure.yml +4 -0
- data/config/cops/markdown/heading_style.yml +9 -0
- data/config/cops/markdown/image_alt_text.yml +3 -0
- data/config/cops/markdown/image_path_separators.yml +3 -0
- data/config/cops/markdown/link_fragment.yml +6 -0
- data/config/cops/markdown/link_style.yml +10 -0
- data/config/cops/markdown/link_text_spacing.yml +3 -0
- data/config/cops/markdown/list_blank_lines.yml +3 -0
- data/config/cops/markdown/list_continuation_indentation.yml +3 -0
- data/config/cops/markdown/list_indentation.yml +3 -0
- data/config/cops/markdown/list_marker_spacing.yml +7 -0
- data/config/cops/markdown/ordered_list_numbering.yml +7 -0
- data/config/cops/markdown/outside_spacing.yml +3 -0
- data/config/cops/markdown/proper_names.yml +5 -0
- data/config/cops/markdown/raw_html.yml +4 -0
- data/config/cops/markdown/reference_definitions.yml +8 -0
- data/config/cops/markdown/reversed_link.yml +3 -0
- data/config/cops/markdown/shell_prompts.yml +13 -0
- data/config/cops/markdown/single_top_level_heading.yml +4 -0
- data/config/cops/markdown/strong_style.yml +4 -0
- data/config/cops/markdown/structural_line_length.yml +9 -0
- data/config/cops/markdown/syntax.yml +3 -0
- data/config/cops/markdown/table_blank_lines.yml +3 -0
- data/config/cops/markdown/table_columns.yml +3 -0
- data/config/cops/markdown/table_pipes.yml +4 -0
- data/config/cops/markdown/table_spacing.yml +4 -0
- data/config/cops/markdown/thematic_break_blank_lines.yml +5 -0
- data/config/cops/markdown/thematic_break_style.yml +4 -0
- data/config/cops/markdown/trailing_whitespace.yml +5 -0
- data/config/cops/markdown/unclosed_fenced_code_block.yml +3 -0
- data/config/cops/markdown/undefined_reference.yml +4 -0
- data/config/cops/markdown/unordered_list_indentation.yml +4 -0
- data/config/cops/markdown/unordered_list_marker.yml +9 -0
- data/config/cops/prose/max_length.yml +5 -0
- data/config/cops/prose/multiple_blank_lines.yml +4 -0
- data/config/cops/prose/trailing_newline.yml +3 -0
- data/config/cops/text/trailing_whitespace.yml +3 -0
- data/config/default.yml +16 -0
- data/docs/cops/markdown/atx_heading_space.md +26 -0
- data/docs/cops/markdown/atx_heading_spacing.md +24 -0
- data/docs/cops/markdown/bare_url.md +30 -0
- data/docs/cops/markdown/blockquote_blank_lines.md +30 -0
- data/docs/cops/markdown/blockquote_spacing.md +30 -0
- data/docs/cops/markdown/closed_heading_space.md +26 -0
- data/docs/cops/markdown/closed_heading_spacing.md +24 -0
- data/docs/cops/markdown/code_block_style.md +37 -0
- data/docs/cops/markdown/code_span_spacing.md +26 -0
- data/docs/cops/markdown/compact_lists.md +29 -0
- data/docs/cops/markdown/descriptive_link_text.md +31 -0
- data/docs/cops/markdown/duplicate_headings.md +35 -0
- data/docs/cops/markdown/emphasis_heading.md +26 -0
- data/docs/cops/markdown/emphasis_spacing.md +24 -0
- data/docs/cops/markdown/emphasis_style.md +34 -0
- data/docs/cops/markdown/empty_link.md +26 -0
- data/docs/cops/markdown/fence_blank_lines.md +43 -0
- data/docs/cops/markdown/fence_language.md +31 -0
- data/docs/cops/markdown/fence_style.md +38 -0
- data/docs/cops/markdown/first_heading.md +31 -0
- data/docs/cops/markdown/hard_break_style.md +50 -0
- data/docs/cops/markdown/hard_tabs.md +30 -0
- data/docs/cops/markdown/heading_blank_lines.md +36 -0
- data/docs/cops/markdown/heading_increment.md +30 -0
- data/docs/cops/markdown/heading_indentation.md +26 -0
- data/docs/cops/markdown/heading_punctuation.md +27 -0
- data/docs/cops/markdown/heading_structure.md +41 -0
- data/docs/cops/markdown/heading_style.md +36 -0
- data/docs/cops/markdown/image_alt_text.md +26 -0
- data/docs/cops/markdown/image_path_separators.md +32 -0
- data/docs/cops/markdown/link_fragment.md +35 -0
- data/docs/cops/markdown/link_style.md +49 -0
- data/docs/cops/markdown/link_text_spacing.md +26 -0
- data/docs/cops/markdown/list_blank_lines.md +31 -0
- data/docs/cops/markdown/list_continuation_indentation.md +28 -0
- data/docs/cops/markdown/list_indentation.md +28 -0
- data/docs/cops/markdown/list_marker_spacing.md +32 -0
- data/docs/cops/markdown/ordered_list_numbering.md +29 -0
- data/docs/cops/markdown/outside_spacing.md +26 -0
- data/docs/cops/markdown/proper_names.md +39 -0
- data/docs/cops/markdown/raw_html.md +27 -0
- data/docs/cops/markdown/reference_definitions.md +36 -0
- data/docs/cops/markdown/reversed_link.md +26 -0
- data/docs/cops/markdown/shell_prompts.md +57 -0
- data/docs/cops/markdown/single_top_level_heading.md +31 -0
- data/docs/cops/markdown/strong_style.md +34 -0
- data/docs/cops/markdown/structural_line_length.md +40 -0
- data/docs/cops/markdown/syntax.md +28 -0
- data/docs/cops/markdown/table_blank_lines.md +34 -0
- data/docs/cops/markdown/table_columns.md +30 -0
- data/docs/cops/markdown/table_pipes.md +38 -0
- data/docs/cops/markdown/table_spacing.md +38 -0
- data/docs/cops/markdown/thematic_break_blank_lines.md +31 -0
- data/docs/cops/markdown/thematic_break_style.md +48 -0
- data/docs/cops/markdown/trailing_whitespace.md +30 -0
- data/docs/cops/markdown/unclosed_fenced_code_block.md +29 -0
- data/docs/cops/markdown/undefined_reference.md +29 -0
- data/docs/cops/markdown/unordered_list_indentation.md +45 -0
- data/docs/cops/markdown/unordered_list_marker.md +36 -0
- data/docs/cops/prose/max_length.md +33 -0
- data/docs/cops/prose/multiple_blank_lines.md +50 -0
- data/docs/cops/prose/trailing_newline.md +26 -0
- data/docs/cops/text/trailing_whitespace.md +26 -0
- data/docs/settings/files.md +33 -0
- data/docs/settings/front_matter.md +12 -0
- data/lib/rubocop/cop/markdown/atx_heading_space.rb +22 -0
- data/lib/rubocop/cop/markdown/atx_heading_spacing.rb +27 -0
- data/lib/rubocop/cop/markdown/bare_url.rb +22 -0
- data/lib/rubocop/cop/markdown/base.rb +17 -0
- data/lib/rubocop/cop/markdown/blockquote_blank_lines.rb +49 -0
- data/lib/rubocop/cop/markdown/blockquote_spacing.rb +76 -0
- data/lib/rubocop/cop/markdown/closed_heading_space.rb +26 -0
- data/lib/rubocop/cop/markdown/closed_heading_spacing.rb +27 -0
- data/lib/rubocop/cop/markdown/code_block_style.rb +80 -0
- data/lib/rubocop/cop/markdown/code_span_spacing.rb +51 -0
- data/lib/rubocop/cop/markdown/compact_lists.rb +43 -0
- data/lib/rubocop/cop/markdown/descriptive_link_text.rb +27 -0
- data/lib/rubocop/cop/markdown/duplicate_headings.rb +28 -0
- data/lib/rubocop/cop/markdown/emphasis_heading.rb +18 -0
- data/lib/rubocop/cop/markdown/emphasis_spacing.rb +30 -0
- data/lib/rubocop/cop/markdown/emphasis_style.rb +34 -0
- data/lib/rubocop/cop/markdown/empty_link.rb +25 -0
- data/lib/rubocop/cop/markdown/fence_blank_lines.rb +72 -0
- data/lib/rubocop/cop/markdown/fence_language.rb +32 -0
- data/lib/rubocop/cop/markdown/fence_style.rb +39 -0
- data/lib/rubocop/cop/markdown/first_heading.rb +44 -0
- data/lib/rubocop/cop/markdown/hard_break_style.rb +65 -0
- data/lib/rubocop/cop/markdown/hard_tabs.rb +32 -0
- data/lib/rubocop/cop/markdown/heading_blank_lines.rb +37 -0
- data/lib/rubocop/cop/markdown/heading_increment.rb +23 -0
- data/lib/rubocop/cop/markdown/heading_indentation.rb +37 -0
- data/lib/rubocop/cop/markdown/heading_punctuation.rb +23 -0
- data/lib/rubocop/cop/markdown/heading_structure.rb +33 -0
- data/lib/rubocop/cop/markdown/heading_style.rb +24 -0
- data/lib/rubocop/cop/markdown/image_alt_text.rb +24 -0
- data/lib/rubocop/cop/markdown/image_path_separators.rb +32 -0
- data/lib/rubocop/cop/markdown/link_fragment.rb +36 -0
- data/lib/rubocop/cop/markdown/link_style.rb +29 -0
- data/lib/rubocop/cop/markdown/link_text_spacing.rb +34 -0
- data/lib/rubocop/cop/markdown/list_blank_lines.rb +38 -0
- data/lib/rubocop/cop/markdown/list_continuation_indentation.rb +59 -0
- data/lib/rubocop/cop/markdown/list_indentation.rb +28 -0
- data/lib/rubocop/cop/markdown/list_marker_spacing.rb +44 -0
- data/lib/rubocop/cop/markdown/ordered_list_numbering.rb +35 -0
- data/lib/rubocop/cop/markdown/outside_spacing.rb +88 -0
- data/lib/rubocop/cop/markdown/proper_names.rb +51 -0
- data/lib/rubocop/cop/markdown/raw_html.rb +60 -0
- data/lib/rubocop/cop/markdown/reference_definitions.rb +37 -0
- data/lib/rubocop/cop/markdown/reversed_link.rb +20 -0
- data/lib/rubocop/cop/markdown/shell_prompts.rb +72 -0
- data/lib/rubocop/cop/markdown/single_top_level_heading.rb +19 -0
- data/lib/rubocop/cop/markdown/strong_style.rb +33 -0
- data/lib/rubocop/cop/markdown/structural_line_length.rb +51 -0
- data/lib/rubocop/cop/markdown/syntax.rb +33 -0
- data/lib/rubocop/cop/markdown/table_blank_lines.rb +46 -0
- data/lib/rubocop/cop/markdown/table_columns.rb +23 -0
- data/lib/rubocop/cop/markdown/table_pipes.rb +45 -0
- data/lib/rubocop/cop/markdown/table_spacing.rb +62 -0
- data/lib/rubocop/cop/markdown/thematic_break_blank_lines.rb +115 -0
- data/lib/rubocop/cop/markdown/thematic_break_style.rb +74 -0
- data/lib/rubocop/cop/markdown/trailing_whitespace.rb +55 -0
- data/lib/rubocop/cop/markdown/unclosed_fenced_code_block.rb +82 -0
- data/lib/rubocop/cop/markdown/undefined_reference.rb +25 -0
- data/lib/rubocop/cop/markdown/unordered_list_indentation.rb +37 -0
- data/lib/rubocop/cop/markdown/unordered_list_marker.rb +45 -0
- data/lib/rubocop/cop/prose/base.rb +55 -0
- data/lib/rubocop/cop/prose/max_length.rb +93 -0
- data/lib/rubocop/cop/prose/multiple_blank_lines.rb +96 -0
- data/lib/rubocop/cop/prose/trailing_newline.rb +26 -0
- data/lib/rubocop/cop/text/base.rb +17 -0
- data/lib/rubocop/cop/text/trailing_whitespace.rb +25 -0
- data/lib/rubocop/prose/code_rules.rb +79 -0
- data/lib/rubocop/prose/document.rb +62 -0
- data/lib/rubocop/prose/file_types.rb +56 -0
- data/lib/rubocop/prose/heading_rules.rb +54 -0
- data/lib/rubocop/prose/inline_rules.rb +213 -0
- data/lib/rubocop/prose/link_rules.rb +630 -0
- data/lib/rubocop/prose/list_rules.rb +65 -0
- data/lib/rubocop/prose/markdown_document.rb +167 -0
- data/lib/rubocop/prose/parser.rb +80 -0
- data/lib/rubocop/prose/plugin.rb +30 -0
- data/lib/rubocop/prose/runner.rb +63 -0
- data/lib/rubocop/prose/source.rb +82 -0
- data/lib/rubocop/prose/structural_blank_lines.rb +90 -0
- data/lib/rubocop/prose/table_rules.rb +71 -0
- data/lib/rubocop/prose/text_document.rb +54 -0
- data/lib/rubocop/prose/version.rb +7 -0
- data/lib/rubocop-prose.rb +30 -0
- metadata +335 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/list_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class BlockquoteBlankLines < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
include RuboCop::Prose::ListRules
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def investigate_document
|
|
15
|
+
inspect_children(document.root)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def inspect_children(parent)
|
|
19
|
+
previous = nil
|
|
20
|
+
parent.children.each do |child|
|
|
21
|
+
if child.type == :blockquote
|
|
22
|
+
connect_quotes(previous, child) if previous
|
|
23
|
+
previous = child
|
|
24
|
+
elsif child.type != :blank
|
|
25
|
+
previous = nil
|
|
26
|
+
end
|
|
27
|
+
inspect_children(child)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def connect_quotes(first, last)
|
|
32
|
+
start = last.options[:location]
|
|
33
|
+
prefix = document.line(start)[/\A[ \t]*(?:>[ \t]*)+/]
|
|
34
|
+
return unless prefix
|
|
35
|
+
|
|
36
|
+
((node_last_line(first) + 1)...start).each do |number|
|
|
37
|
+
next unless container_blank?(number)
|
|
38
|
+
|
|
39
|
+
range = document.line_range(number)
|
|
40
|
+
prose_offense(range,
|
|
41
|
+
message: 'Keep blockquote markers on blank lines between quoted blocks.') do |corrector|
|
|
42
|
+
corrector.replace(processed_range(range), prefix.rstrip)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/list_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class BlockquoteSpacing < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
include RuboCop::Prose::ListRules
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def investigate_document
|
|
15
|
+
quote_depths = Hash.new(0)
|
|
16
|
+
document.nodes(:blockquote).each do |node|
|
|
17
|
+
(node.options[:location]..node_last_line(node)).each { |number| quote_depths[number] += 1 }
|
|
18
|
+
end
|
|
19
|
+
list_lines = []
|
|
20
|
+
list_groups do |_list, marks, _parent|
|
|
21
|
+
marks.each { |mark| list_lines.concat((mark[:line]..node_last_line(mark[:item])).to_a) }
|
|
22
|
+
end
|
|
23
|
+
document.prose_lines do |text, number|
|
|
24
|
+
next unless quote_depths[number].positive?
|
|
25
|
+
|
|
26
|
+
prefix = text[/\A[ \t]*(?:>[ \t]*)+/]
|
|
27
|
+
next unless prefix
|
|
28
|
+
|
|
29
|
+
markers = prefix.to_enum(:scan, />/).map { Regexp.last_match.begin(0) }
|
|
30
|
+
prefix.to_enum(:scan, />[ \t]{2,}/).each do
|
|
31
|
+
match = Regexp.last_match
|
|
32
|
+
next if match.end(0) == prefix.length && (list_lines.include?(number) || text[prefix.length..].empty?)
|
|
33
|
+
|
|
34
|
+
range = document.range(number, match.begin(0) + 1, number, match.end(0))
|
|
35
|
+
prose_offense(range, message: 'Use one space after a blockquote marker.') do |corrector|
|
|
36
|
+
corrector.replace(processed_range(range), ' ')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
next unless markers.length == quote_depths[number]
|
|
41
|
+
|
|
42
|
+
markers.each do |column|
|
|
43
|
+
next if text[column + 1]&.match?(/[ \t]/)
|
|
44
|
+
next if text[(column + 1)..].sub(/\A(?:[ \t]*>)*[ \t]*/, '').empty?
|
|
45
|
+
|
|
46
|
+
range = document.range(number, column, number, column + 1)
|
|
47
|
+
prose_offense(range, message: 'Use one space after a blockquote marker.') do |corrector|
|
|
48
|
+
next unless correction_preserves_structure?(number, column)
|
|
49
|
+
|
|
50
|
+
corrector.insert_after(processed_range(range), ' ')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def correction_preserves_structure?(number, column)
|
|
57
|
+
source = document.source.dup
|
|
58
|
+
line_start = document.buffer.line_range(number).begin_pos
|
|
59
|
+
position = source.byteslice(0, line_start).length + column + 1
|
|
60
|
+
source.insert(position, ' ')
|
|
61
|
+
candidate = RuboCop::Prose::MarkdownDocument.new(
|
|
62
|
+
::Parser::Source::Buffer.new(document.buffer.name, source: source),
|
|
63
|
+
configuration: config
|
|
64
|
+
)
|
|
65
|
+
tree_signature(document.root) == tree_signature(candidate.root) &&
|
|
66
|
+
document.warnings == candidate.warnings
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def tree_signature(node)
|
|
70
|
+
options = node.options.except(:prose_lines, :original_ranges)
|
|
71
|
+
[node.type, node.value, options, node.children.map { |child| tree_signature(child) }]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class ClosedHeadingSpace < Base
|
|
9
|
+
include RuboCop::Prose::HeadingRules
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
heading_entries.each do |heading|
|
|
13
|
+
opening = heading[:opening]
|
|
14
|
+
next unless opening && !heading[:closing]
|
|
15
|
+
|
|
16
|
+
# An escaped final hash is text, not an intended closing marker.
|
|
17
|
+
ending = /(?<slashes>\\*)(?<hashes>#+)[ \t]*\z/.match(opening[:rest])
|
|
18
|
+
next unless ending && ending[:slashes].length.even?
|
|
19
|
+
|
|
20
|
+
prose_offense(heading_range(heading), message: 'Separate closing heading hashes from text with a space.')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class ClosedHeadingSpacing < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
include RuboCop::Prose::HeadingRules
|
|
11
|
+
|
|
12
|
+
def investigate_document
|
|
13
|
+
heading_entries.each do |heading|
|
|
14
|
+
closing = heading[:closing]
|
|
15
|
+
next unless closing && closing[:space].length > 1
|
|
16
|
+
|
|
17
|
+
column = heading[:opening].begin(:rest) + closing.begin(:space)
|
|
18
|
+
range = document.range(heading[:line], column, heading[:line], column + closing[:space].length)
|
|
19
|
+
prose_offense(range, message: 'Use one space before closing heading hashes.') do |corrector|
|
|
20
|
+
corrector.replace(processed_range(range), ' ')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/code_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class CodeBlockStyle < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
include RuboCop::Prose::CodeRules
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def investigate_document
|
|
15
|
+
expected = cop_config.fetch('EnforcedStyle', 'consistent')
|
|
16
|
+
code_blocks.each do |node|
|
|
17
|
+
actual = node.options[:fenced] ? 'fenced' : 'indented'
|
|
18
|
+
expected = actual if expected == 'consistent'
|
|
19
|
+
next if actual == expected
|
|
20
|
+
|
|
21
|
+
range = document.line_range(node.options[:location])
|
|
22
|
+
prose_offense(range, message: "Use #{expected} code blocks.") do |corrector|
|
|
23
|
+
conversion = safe_conversion(node, expected)
|
|
24
|
+
corrector.replace(processed_range(conversion.first), conversion.last) if conversion
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def safe_conversion(node, expected)
|
|
30
|
+
return unless code_ancestors(node).all? { |ancestor| ancestor.type == :root }
|
|
31
|
+
|
|
32
|
+
expected == 'fenced' ? to_fenced(node) : to_indented(node)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_fenced(node)
|
|
36
|
+
first = node.options.fetch(:code_start_line)
|
|
37
|
+
last = first + node.value.lines.length - 1
|
|
38
|
+
return if last < first || !code_blank?(first - 1) || !code_blank?(last + 1)
|
|
39
|
+
|
|
40
|
+
raw = document.lines[(first - 1)..(last - 1)]
|
|
41
|
+
return unless raw.all? { |line| line.start_with?(' ') || line.empty? }
|
|
42
|
+
return unless "#{raw.map { |line| line.delete_prefix(' ') }.join("\n")}\n" == node.value
|
|
43
|
+
|
|
44
|
+
length = [3, node.value.scan(/`+/).map(&:length).max.to_i + 1].max
|
|
45
|
+
fence = '`' * length
|
|
46
|
+
[document.range(first, 0, last), "#{fence}\n#{node.value}#{fence}"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_indented(node)
|
|
50
|
+
details = fence_details(node)
|
|
51
|
+
return unless details && details[:opening][:prefix].empty? && details[:opening][:info].strip.empty?
|
|
52
|
+
return unless details[:closing][:prefix].empty?
|
|
53
|
+
return unless code_blank?(details[:first] - 1) && code_blank?(details[:last] + 1)
|
|
54
|
+
return if adjacent_code_block?(node, details)
|
|
55
|
+
|
|
56
|
+
body = document.lines[details[:first]...(details[:last] - 1)]
|
|
57
|
+
return if body.empty? || body.first.strip.empty? || body.last.strip.empty?
|
|
58
|
+
return unless "#{body.join("\n")}\n" == node.value
|
|
59
|
+
|
|
60
|
+
[document.range(details[:first], 0, details[:last]),
|
|
61
|
+
body.map { |line| line.empty? ? '' : " #{line}" }.join("\n")]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def adjacent_code_block?(node, details)
|
|
65
|
+
blocks = code_blocks
|
|
66
|
+
index = blocks.index(node)
|
|
67
|
+
neighbors = []
|
|
68
|
+
neighbors << blocks[index - 1] if index.positive?
|
|
69
|
+
neighbors << blocks[index + 1] if index + 1 < blocks.length
|
|
70
|
+
neighbors.any? do |other|
|
|
71
|
+
first = other.options[:location]
|
|
72
|
+
last = other.options[:code_start_line] + other.value.lines.length - (other.options[:fenced] ? 0 : 1)
|
|
73
|
+
gap = last < details[:first] ? ((last + 1)...details[:first]) : ((details[:last] + 1)...first)
|
|
74
|
+
gap.all? { |number| document.line(number).strip.empty? }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/inline_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class CodeSpanSpacing < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
multiline = RuboCop::Prose::InlineRules.multiline_atoms(document)
|
|
13
|
+
.select { |range| range.source.start_with?('`') }
|
|
14
|
+
RuboCop::Prose::InlineRules.lines(document).each do |text, number, column|
|
|
15
|
+
RuboCop::Prose::InlineRules.spans(text).each do |span|
|
|
16
|
+
next unless span.kind == :code && span.begin_pos >= column
|
|
17
|
+
|
|
18
|
+
position = document.range(number, span.begin_pos, number, span.end_pos)
|
|
19
|
+
next if multiline.any? { |range| range.overlaps?(position) }
|
|
20
|
+
|
|
21
|
+
width = span.delimiter.length
|
|
22
|
+
content = text[(span.begin_pos + width)...(span.end_pos - width)]
|
|
23
|
+
next if content.strip.empty? || content == content.strip
|
|
24
|
+
# One space on each side is the Markdown escape for literal edge backticks.
|
|
25
|
+
next if content.start_with?(' ') && content.end_with?(' ') &&
|
|
26
|
+
(content[1] == '`' || content[-2] == '`')
|
|
27
|
+
|
|
28
|
+
range = document.range(number, span.begin_pos, number, span.end_pos)
|
|
29
|
+
prose_offense(range, message: 'Avoid unnecessary padding inside code spans.') do |corrector|
|
|
30
|
+
# Only symmetric single-space padding can be removed without changing the value.
|
|
31
|
+
next unless content.start_with?(' ') && content.end_with?(' ')
|
|
32
|
+
next if content[1] == ' ' || content[-2] == ' '
|
|
33
|
+
|
|
34
|
+
corrector.replace(processed_range(range), "#{span.delimiter}#{content[1...-1]}#{span.delimiter}")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
multiline.each do |range|
|
|
38
|
+
delimiter = range.source[/\A`+/]
|
|
39
|
+
content = range.source[delimiter.length...-delimiter.length]
|
|
40
|
+
next if content.strip.empty? || content == content.strip
|
|
41
|
+
next if content.start_with?(' ') && content.end_with?(' ') &&
|
|
42
|
+
(content[1] == '`' || content[-2] == '`')
|
|
43
|
+
|
|
44
|
+
prose_offense(range, message: 'Avoid unnecessary padding inside code spans.')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/list_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class CompactLists < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
include RuboCop::Prose::ListRules
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def investigate_document
|
|
15
|
+
list_groups do |list, marks, _parent|
|
|
16
|
+
items = list.children.select { |child| child.type == :li }
|
|
17
|
+
next unless marks.length == items.length
|
|
18
|
+
|
|
19
|
+
marks.each_cons(2) do |previous, following|
|
|
20
|
+
first_blank = node_last_line(previous[:item]) + 1
|
|
21
|
+
last_blank = following[:line] - 1
|
|
22
|
+
next unless first_blank <= last_blank
|
|
23
|
+
|
|
24
|
+
blank_lines = (first_blank..last_blank).to_a
|
|
25
|
+
next unless blank_lines.all? { |number| container_blank?(number) }
|
|
26
|
+
|
|
27
|
+
range = document.range(first_blank, 0, following[:line], 0)
|
|
28
|
+
prose_offense(range, message: 'Avoid blank lines between list items.') do |corrector|
|
|
29
|
+
next unless simple_list_item?(previous[:item]) && simple_list_item?(following[:item])
|
|
30
|
+
|
|
31
|
+
corrector.remove(processed_range(range))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def simple_list_item?(item)
|
|
38
|
+
item.children.one? && item.children.first.type == :p
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/link_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class DescriptiveLinkText < Base
|
|
9
|
+
MSG = 'Use descriptive link text instead of a generic label.'
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def investigate_document
|
|
14
|
+
scanner = document.link_rules
|
|
15
|
+
generic = cop_config.fetch('GenericLabels', ['click here', 'here', 'link', 'more'])
|
|
16
|
+
.map { |label| scanner.normalize_text(label) }
|
|
17
|
+
scanner.links.each do |link|
|
|
18
|
+
next if link.image || link.style == 'autolink' || !scanner.resolved?(link)
|
|
19
|
+
next unless generic.include?(scanner.normalize_text(scanner.display_text(link)))
|
|
20
|
+
|
|
21
|
+
prose_offense(scanner.label_range(link))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class DuplicateHeadings < Base
|
|
9
|
+
include RuboCop::Prose::HeadingRules
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
seen = Set.new
|
|
13
|
+
parents = []
|
|
14
|
+
heading_entries.each do |heading|
|
|
15
|
+
parents.pop while parents.any? && parents.last[:level] >= heading[:level]
|
|
16
|
+
scope = cop_config.fetch('Scope', 'siblings') == 'siblings' ? parents.last&.fetch(:line) : nil
|
|
17
|
+
key = [scope, heading[:text]]
|
|
18
|
+
if seen.include?(key)
|
|
19
|
+
prose_offense(heading_range(heading), message: 'Use unique heading text within the configured scope.')
|
|
20
|
+
end
|
|
21
|
+
seen.add(key)
|
|
22
|
+
parents << heading
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Markdown
|
|
6
|
+
class EmphasisHeading < Base
|
|
7
|
+
def investigate_paragraph(paragraph)
|
|
8
|
+
children = paragraph.children.reject { |child| child.type == :text && child.value.strip.empty? }
|
|
9
|
+
return unless children.one? && %i[em strong].include?(children.first.type)
|
|
10
|
+
return unless document.line_numbers(paragraph).size == 1
|
|
11
|
+
|
|
12
|
+
number = paragraph.options.fetch(:location)
|
|
13
|
+
prose_offense(document.line_range(number), message: 'Use a heading instead of an emphasis-only paragraph.')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/inline_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class EmphasisSpacing < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
RuboCop::Prose::InlineRules.lines(document).each do |text, number, column|
|
|
13
|
+
masked = RuboCop::Prose::InlineRules.mask(text, document: document, number: number)
|
|
14
|
+
masked.to_enum(:scan, /(?<![\w*_])(\*{1,3}|_{1,3})(?![*_])(.+?)\1(?![\w*_])/).each do
|
|
15
|
+
match = Regexp.last_match
|
|
16
|
+
content = text[match.begin(2)...match.end(2)]
|
|
17
|
+
next if match.begin(0) < column || content.strip.empty? || content == content.strip
|
|
18
|
+
|
|
19
|
+
range = document.range(number, match.begin(0), number, match.end(0))
|
|
20
|
+
replacement = "#{match[1]}#{text[match.begin(2)...match.end(2)].strip}#{match[1]}"
|
|
21
|
+
prose_offense(range, message: 'Remove spaces inside emphasis delimiters.') do |corrector|
|
|
22
|
+
corrector.replace(processed_range(range), replacement)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/inline_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class EmphasisStyle < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
expected = cop_config.fetch('Style', 'consistent')
|
|
13
|
+
document.emphasis_ranges(:em).each do |opening, closing, char|
|
|
14
|
+
actual = char == '*' ? 'asterisk' : 'underscore'
|
|
15
|
+
expected = actual if expected == 'consistent'
|
|
16
|
+
next if actual == expected
|
|
17
|
+
|
|
18
|
+
replacement = expected == 'asterisk' ? '*' : '_'
|
|
19
|
+
# Underscores cannot delimit intraword emphasis.
|
|
20
|
+
before = opening.begin_pos.positive? ? document.source[opening.begin_pos - 1] : nil
|
|
21
|
+
after = document.source[closing.end_pos]
|
|
22
|
+
|
|
23
|
+
prose_offense(opening, message: "Use #{expected} emphasis.") do |corrector|
|
|
24
|
+
next if replacement == '_' && (before&.match?(/[[:alnum:]_]/) || after&.match?(/[[:alnum:]_]/))
|
|
25
|
+
|
|
26
|
+
corrector.replace(processed_range(opening), replacement)
|
|
27
|
+
corrector.replace(processed_range(closing), replacement)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/link_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class EmptyLink < Base
|
|
9
|
+
MSG = 'Provide a nonempty link destination.'
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def investigate_document
|
|
14
|
+
scanner = document.link_rules
|
|
15
|
+
scanner.links.each do |link|
|
|
16
|
+
next if link.image || !scanner.resolved?(link)
|
|
17
|
+
next unless ['', '#'].include?(scanner.destination(link))
|
|
18
|
+
|
|
19
|
+
prose_offense(scanner.link_range(link))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/code_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class FenceBlankLines < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
include RuboCop::Prose::CodeRules
|
|
11
|
+
|
|
12
|
+
def validate_config
|
|
13
|
+
%w[LinesAbove LinesBelow].each do |name|
|
|
14
|
+
value = cop_config[name]
|
|
15
|
+
next if value.is_a?(Integer) && value >= 0
|
|
16
|
+
|
|
17
|
+
raise ValidationError, "Markdown/FenceBlankLines: #{name} must be a nonnegative integer."
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def investigate_document
|
|
24
|
+
@insertions = {}
|
|
25
|
+
fenced_blocks(list_items: cop_config.fetch('ListItems', true)) do |details|
|
|
26
|
+
first = details[:first]
|
|
27
|
+
last = details[:last]
|
|
28
|
+
prefix = code_continuation_prefix(details[:opening][:prefix]).rstrip
|
|
29
|
+
inline_item = details[:opening][:prefix].match?(/[-+*]|\d+[.)]/)
|
|
30
|
+
require_blanks(first, prefix, cop_config.fetch('LinesAbove', 1), -1) unless inline_item
|
|
31
|
+
require_blanks(last + 1, prefix, cop_config.fetch('LinesBelow', 1), 1)
|
|
32
|
+
end
|
|
33
|
+
@insertions.each do |number, insertion|
|
|
34
|
+
add_blank(number, insertion[:prefix], insertion[:count])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def require_blanks(number, prefix, required, direction)
|
|
39
|
+
return if required.zero?
|
|
40
|
+
|
|
41
|
+
existing = blank_count(direction.negative? ? number - 1 : number, direction)
|
|
42
|
+
missing = required - existing
|
|
43
|
+
return unless missing.positive?
|
|
44
|
+
|
|
45
|
+
# Adjacent fences must share one insertion, even when some padding already exists.
|
|
46
|
+
number -= existing if direction.negative?
|
|
47
|
+
insertion = @insertions[number] ||= { prefix: prefix, count: missing }
|
|
48
|
+
insertion[:count] = [insertion[:count], missing].max
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def blank_count(number, direction)
|
|
52
|
+
count = 0
|
|
53
|
+
while number.between?(1, document.lines.length)
|
|
54
|
+
return Float::INFINITY if direction.negative? && document.front_matter_line?(number)
|
|
55
|
+
break unless code_blank?(number)
|
|
56
|
+
|
|
57
|
+
count += 1
|
|
58
|
+
number += direction
|
|
59
|
+
end
|
|
60
|
+
number.between?(1, document.lines.length) ? count : Float::INFINITY
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_blank(number, prefix, count)
|
|
64
|
+
range = document.line_range(number)
|
|
65
|
+
prose_offense(range, message: 'Surround fenced code blocks with blank lines.') do |corrector|
|
|
66
|
+
corrector.insert_before(processed_range(range), "#{prefix}\n" * count)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/code_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class FenceLanguage < Base
|
|
9
|
+
include RuboCop::Prose::CodeRules
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def investigate_document
|
|
14
|
+
allowed = cop_config.fetch('AllowedLanguages', [])
|
|
15
|
+
code_blocks.each do |node|
|
|
16
|
+
next unless node.options[:fenced]
|
|
17
|
+
|
|
18
|
+
language = node.options[:lang].to_s
|
|
19
|
+
next unless language.empty? || (!allowed.empty? && !allowed.include?(language))
|
|
20
|
+
|
|
21
|
+
message = if language.empty?
|
|
22
|
+
'Specify a language for this fenced code block.'
|
|
23
|
+
else
|
|
24
|
+
"Use an allowed fence language (#{allowed.join(', ')})."
|
|
25
|
+
end
|
|
26
|
+
prose_offense(document.line_range(node.options[:location]), message: message)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/code_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class FenceStyle < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
include RuboCop::Prose::CodeRules
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def investigate_document
|
|
15
|
+
expected = { 'backtick' => '`', 'tilde' => '~' }[cop_config.fetch('EnforcedStyle', 'consistent')]
|
|
16
|
+
code_blocks.each do |node|
|
|
17
|
+
details = fence_details(node)
|
|
18
|
+
next unless details
|
|
19
|
+
|
|
20
|
+
expected ||= details[:opening][:fence][0]
|
|
21
|
+
next if details[:opening][:fence][0] == expected
|
|
22
|
+
|
|
23
|
+
range = fence_range(details[:first], details[:opening])
|
|
24
|
+
prose_offense(range, message: "Use #{expected == '`' ? 'backtick' : 'tilde'} fences.") do |corrector|
|
|
25
|
+
next if expected == '`' && details[:opening][:info].include?('`')
|
|
26
|
+
|
|
27
|
+
length = [details[:opening][:fence].length, details[:closing][:fence].length,
|
|
28
|
+
node.value.scan(/#{Regexp.escape(expected)}+/).map(&:length).max.to_i + 1].max
|
|
29
|
+
replacement = expected * length
|
|
30
|
+
corrector.replace(processed_range(range), replacement)
|
|
31
|
+
closing = fence_range(details[:last], details[:closing])
|
|
32
|
+
corrector.replace(processed_range(closing), replacement)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|