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,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class FirstHeading < Base
|
|
9
|
+
include RuboCop::Prose::HeadingRules
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
return if front_matter_title?
|
|
13
|
+
|
|
14
|
+
first = document.lines.each_with_index.find do |text, index|
|
|
15
|
+
!document.front_matter_line?(index + 1) && !text.strip.empty?
|
|
16
|
+
end
|
|
17
|
+
return unless first
|
|
18
|
+
|
|
19
|
+
number = first.last + 1
|
|
20
|
+
heading = heading_entries.first
|
|
21
|
+
return if heading && heading[:line] == number && heading[:level] == cop_config.fetch('Level', 1)
|
|
22
|
+
|
|
23
|
+
prose_offense(document.line_range(number),
|
|
24
|
+
message: 'Start the document with a heading at the configured level.')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def front_matter_title?
|
|
30
|
+
return false unless cop_config.fetch('FrontMatterTitle', true) && document.front_matter
|
|
31
|
+
|
|
32
|
+
lines = document.front_matter.lines
|
|
33
|
+
metadata = YAML.safe_load(lines[1...-1].join)
|
|
34
|
+
return false unless metadata.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
title = metadata[cop_config.fetch('TitleKey', 'title')]
|
|
37
|
+
title.is_a?(String) && !title.strip.empty?
|
|
38
|
+
rescue Psych::Exception
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Markdown
|
|
6
|
+
class HardBreakStyle < Base
|
|
7
|
+
extend AutoCorrector
|
|
8
|
+
|
|
9
|
+
MSG = 'Use the configured Markdown hard-break style.'
|
|
10
|
+
STYLES = %w[backslash spaces].freeze
|
|
11
|
+
|
|
12
|
+
def validate_config
|
|
13
|
+
style = cop_config['Style']
|
|
14
|
+
return if STYLES.include?(style)
|
|
15
|
+
|
|
16
|
+
raise ValidationError, 'Markdown/HardBreakStyle: Style must be backslash or spaces.'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def investigate_document
|
|
22
|
+
document.hard_breaks.each { |node| investigate_break(node) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def investigate_break(node)
|
|
26
|
+
number = node.options[:location]
|
|
27
|
+
text = document.line(number)
|
|
28
|
+
marker = marker_for(text)
|
|
29
|
+
return unless marker
|
|
30
|
+
|
|
31
|
+
style, first_column, last_column = marker
|
|
32
|
+
return if style.to_s == cop_config.fetch('Style', 'backslash')
|
|
33
|
+
|
|
34
|
+
range = document.range(number, first_column, number, last_column)
|
|
35
|
+
prose_offense(range) do |corrector|
|
|
36
|
+
next unless safe_to_correct?(style, text, first_column)
|
|
37
|
+
|
|
38
|
+
replacement = cop_config['Style'] == 'backslash' ? '\\' : ' '
|
|
39
|
+
correction_range = if style == :backslash
|
|
40
|
+
document.range(number, last_column - 1, number, last_column)
|
|
41
|
+
else
|
|
42
|
+
range
|
|
43
|
+
end
|
|
44
|
+
corrector.replace(processed_range(correction_range), replacement)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def marker_for(text)
|
|
49
|
+
if (spaces = / {2,}\z/.match(text))
|
|
50
|
+
[:spaces, spaces.begin(0), spaces.end(0)]
|
|
51
|
+
elsif (slashes = /\\+\z/.match(text))
|
|
52
|
+
[:backslash, slashes.begin(0), slashes.end(0)]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def safe_to_correct?(style, text, first_column)
|
|
57
|
+
return false unless processed_source.raw_source == processed_source.buffer.source
|
|
58
|
+
return true if style == :backslash
|
|
59
|
+
|
|
60
|
+
!text[...first_column].end_with?('\\')
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Markdown
|
|
6
|
+
class HardTabs < Base
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def investigate_document
|
|
10
|
+
allowed = cop_config['AllowedCodeLanguages'].map(&:downcase)
|
|
11
|
+
exempt = document.nodes(:codeblock).flat_map do |block|
|
|
12
|
+
next [] unless allowed.include?(block.options[:lang].to_s.split.first.to_s.downcase)
|
|
13
|
+
|
|
14
|
+
first = block.options.fetch(:code_start_line)
|
|
15
|
+
(first...(first + block.value.lines.length)).to_a
|
|
16
|
+
end.to_set
|
|
17
|
+
document.lines.each_with_index do |text, index|
|
|
18
|
+
number = index + 1
|
|
19
|
+
next if document.front_matter_line?(number) || exempt.include?(number)
|
|
20
|
+
next if document.code_line?(number) && !cop_config['CodeBlocks']
|
|
21
|
+
|
|
22
|
+
text.to_enum(:scan, /\t/).each do
|
|
23
|
+
position = Regexp.last_match.begin(0)
|
|
24
|
+
prose_offense(document.range(number, position, number, position + 1),
|
|
25
|
+
message: 'Use spaces instead of hard tabs.')
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class HeadingBlankLines < Base
|
|
9
|
+
include RuboCop::Prose::HeadingRules
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
heading_entries.each do |heading|
|
|
13
|
+
before = blank_count(heading[:line] - 1, -1)
|
|
14
|
+
after = blank_count(heading[:last_line] + 1, 1)
|
|
15
|
+
next unless before < cop_config.fetch('LinesAbove', 1) || after < cop_config.fetch('LinesBelow', 1)
|
|
16
|
+
|
|
17
|
+
prose_offense(heading_range(heading), message: 'Surround headings with the configured blank lines.')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def blank_count(number, direction)
|
|
24
|
+
count = 0
|
|
25
|
+
while number.between?(1, document.lines.length)
|
|
26
|
+
return Float::INFINITY if document.front_matter_line?(number) && direction.negative?
|
|
27
|
+
break unless heading_blank?(number)
|
|
28
|
+
|
|
29
|
+
count += 1
|
|
30
|
+
number += direction
|
|
31
|
+
end
|
|
32
|
+
number.between?(1, document.lines.length) ? count : Float::INFINITY
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class HeadingIncrement < Base
|
|
9
|
+
include RuboCop::Prose::HeadingRules
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
previous = nil
|
|
13
|
+
heading_entries.each do |heading|
|
|
14
|
+
if previous && heading[:level] > previous + 1
|
|
15
|
+
prose_offense(heading_range(heading), message: 'Increase heading levels by at most one.')
|
|
16
|
+
end
|
|
17
|
+
previous = heading[:level]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class HeadingIndentation < Base
|
|
9
|
+
include RuboCop::Prose::HeadingRules
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
parsed_lines = heading_entries.to_set { |heading| heading[:line] }
|
|
13
|
+
document.prose_lines.each do |text, number|
|
|
14
|
+
next unless text.match?(/\A[ \t]+\S/)
|
|
15
|
+
next unless parsed_lines.include?(number) || dedented_heading?(text, number)
|
|
16
|
+
|
|
17
|
+
prose_offense(document.line_range(number), message: 'Start headings without leading indentation.')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def dedented_heading?(text, number)
|
|
24
|
+
return false unless text.match?(/\A {1,3}\S/)
|
|
25
|
+
|
|
26
|
+
source = "#{text.lstrip}\n"
|
|
27
|
+
following = document.lines[number]
|
|
28
|
+
if following&.match?(/\A {0,3}(?:=+|-+)[ \t]*\z/) && !document.code_line?(number + 1)
|
|
29
|
+
source += "#{following.lstrip}\n"
|
|
30
|
+
end
|
|
31
|
+
root = RuboCop::Prose::Parser.parse(source).first
|
|
32
|
+
root.children.any? { |node| node.type == :header }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class HeadingPunctuation < Base
|
|
9
|
+
include RuboCop::Prose::HeadingRules
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
punctuation = cop_config.fetch('Punctuation', '.,;:!?')
|
|
13
|
+
heading_entries.each do |heading|
|
|
14
|
+
ending = heading[:text].rstrip[-1]
|
|
15
|
+
next unless ending && punctuation.include?(ending)
|
|
16
|
+
|
|
17
|
+
prose_offense(heading_range(heading), message: 'Do not end headings with configured punctuation.')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class HeadingStructure < Base
|
|
9
|
+
include RuboCop::Prose::HeadingRules
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
expected = cop_config.fetch('Headings', []).map do |heading|
|
|
13
|
+
match = /\A(\#{1,6}) (.*)\z/.match(heading)
|
|
14
|
+
raise ArgumentError, 'Headings must contain ATX outline strings such as "# Title".' unless match
|
|
15
|
+
|
|
16
|
+
[match[1].length, match[2]]
|
|
17
|
+
end
|
|
18
|
+
return if expected.empty?
|
|
19
|
+
|
|
20
|
+
headings = heading_entries
|
|
21
|
+
headings.each_with_index do |heading, index|
|
|
22
|
+
next if expected[index] == [heading[:level], heading[:text]]
|
|
23
|
+
|
|
24
|
+
prose_offense(heading_range(heading), message: 'Match the configured exact heading outline.')
|
|
25
|
+
end
|
|
26
|
+
return unless expected.length > headings.length
|
|
27
|
+
|
|
28
|
+
prose_offense(heading_end_range, message: 'Add the missing headings from the configured exact outline.')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/heading_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class HeadingStyle < Base
|
|
9
|
+
include RuboCop::Prose::HeadingRules
|
|
10
|
+
|
|
11
|
+
def investigate_document
|
|
12
|
+
headings = heading_entries
|
|
13
|
+
style = cop_config.fetch('EnforcedStyle', 'consistent')
|
|
14
|
+
style = headings.first&.fetch(:style) if style == 'consistent'
|
|
15
|
+
headings.each do |heading|
|
|
16
|
+
next if heading[:style] == style
|
|
17
|
+
|
|
18
|
+
prose_offense(heading_range(heading), message: "Use #{style} headings.")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/link_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class ImageAltText < Base
|
|
9
|
+
MSG = 'Provide alternative text for the image.'
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def investigate_document
|
|
14
|
+
scanner = document.link_rules
|
|
15
|
+
scanner.links.each do |link|
|
|
16
|
+
next unless link.image && scanner.resolved?(link) && scanner.display_text(link).empty?
|
|
17
|
+
|
|
18
|
+
prose_offense(scanner.link_range(link))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/link_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class ImagePathSeparators < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
|
|
11
|
+
MSG = 'Use forward slashes in local image destinations.'
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def investigate_document
|
|
16
|
+
scanner = document.link_rules
|
|
17
|
+
reported = Set.new
|
|
18
|
+
scanner.links.each do |link|
|
|
19
|
+
scanner.image_path_separators(link).each do |position, ambiguous|
|
|
20
|
+
next unless reported.add?(position)
|
|
21
|
+
|
|
22
|
+
range = scanner.range(position, position + 1)
|
|
23
|
+
prose_offense(range) do |corrector|
|
|
24
|
+
corrector.replace(processed_range(range), '/') unless ambiguous
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/link_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class LinkFragment < Base
|
|
9
|
+
MSG = 'Link fragment does not identify a heading or HTML anchor in this document.'
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def investigate_document
|
|
14
|
+
renderer = cop_config.fetch('Renderer', 'github')
|
|
15
|
+
raise ArgumentError, "Unknown anchor renderer: #{renderer}" unless %w[github gitlab
|
|
16
|
+
kramdown].include?(renderer)
|
|
17
|
+
|
|
18
|
+
scanner = document.link_rules
|
|
19
|
+
anchors = scanner.anchors(renderer, cop_config.fetch('AdditionalAnchors', []))
|
|
20
|
+
scanner.links.each do |link|
|
|
21
|
+
next if link.image || !scanner.resolved?(link)
|
|
22
|
+
|
|
23
|
+
target = scanner.destination(link)
|
|
24
|
+
next unless target&.start_with?('#')
|
|
25
|
+
next if target == '#' && cop_config.fetch('AllowEmptyFragment', true)
|
|
26
|
+
|
|
27
|
+
fragment = URI::DEFAULT_PARSER.unescape(target[1..])
|
|
28
|
+
next if anchors.include?(fragment)
|
|
29
|
+
|
|
30
|
+
prose_offense(scanner.link_range(link))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/link_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class LinkStyle < Base
|
|
9
|
+
MSG = 'Use an allowed %<kind>s style instead of %<style>s.'
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def investigate_document
|
|
14
|
+
scanner = document.link_rules
|
|
15
|
+
scanner.links.each do |link|
|
|
16
|
+
next unless scanner.resolved?(link)
|
|
17
|
+
|
|
18
|
+
key = link.image ? 'AllowedImageStyles' : 'AllowedLinkStyles'
|
|
19
|
+
allowed = cop_config.fetch(key, %w[inline reference autolink])
|
|
20
|
+
next if allowed.include?(link.style) || (link.reference && allowed.include?('reference'))
|
|
21
|
+
|
|
22
|
+
prose_offense(scanner.link_range(link),
|
|
23
|
+
message: format(MSG, kind: link.image ? 'image' : 'link', style: link.style))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/link_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class LinkTextSpacing < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
|
|
11
|
+
MSG = 'Remove whitespace at the edges of link text.'
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def investigate_document
|
|
16
|
+
scanner = document.link_rules
|
|
17
|
+
scanner.links.each do |link|
|
|
18
|
+
next if link.image || link.style == 'autolink' || !scanner.resolved?(link)
|
|
19
|
+
next if link.label == link.label.strip
|
|
20
|
+
|
|
21
|
+
original = scanner.label_range(link)
|
|
22
|
+
if link.label.strip.empty?
|
|
23
|
+
prose_offense(original)
|
|
24
|
+
next
|
|
25
|
+
end
|
|
26
|
+
prose_offense(original) do |corrector|
|
|
27
|
+
corrector.replace(processed_range(original), link.label.strip)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/list_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class ListBlankLines < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
include RuboCop::Prose::ListRules
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def investigate_document
|
|
15
|
+
@insertions = Set.new
|
|
16
|
+
list_groups do |list, marks, parent|
|
|
17
|
+
next if parent
|
|
18
|
+
|
|
19
|
+
first = marks.first[:line]
|
|
20
|
+
last = node_last_line(list)
|
|
21
|
+
prefix = continuation_prefix(document.line(first)[0...marks.first[:column]])
|
|
22
|
+
add_boundary(first, prefix) unless container_blank?(first - 1) || document.front_matter_line?(first - 1)
|
|
23
|
+
add_boundary(last + 1, prefix) unless container_blank?(last + 1)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_boundary(number, prefix)
|
|
28
|
+
return unless @insertions.add?(number)
|
|
29
|
+
|
|
30
|
+
range = document.line_range(number)
|
|
31
|
+
prose_offense(range, message: 'Surround lists with blank lines.') do |corrector|
|
|
32
|
+
corrector.insert_before(processed_range(range), "#{prefix.rstrip}\n")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/list_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class ListContinuationIndentation < Base
|
|
9
|
+
include RuboCop::Prose::ListRules
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def investigate_document
|
|
14
|
+
list_groups do |_list, marks, _parent|
|
|
15
|
+
marks.each do |mark|
|
|
16
|
+
item_paragraphs(mark[:item]).each do |paragraph|
|
|
17
|
+
investigate_continuations(mark, paragraph)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def item_paragraphs(node)
|
|
24
|
+
return [] if %i[ul ol].include?(node.type)
|
|
25
|
+
return [node] if node.type == :p && node.options[:prose_source]
|
|
26
|
+
|
|
27
|
+
node.children.flat_map { |child| item_paragraphs(child) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def investigate_continuations(mark, paragraph)
|
|
31
|
+
lines = document.line_numbers(paragraph).to_a
|
|
32
|
+
return if lines.length < 2
|
|
33
|
+
|
|
34
|
+
first_line = lines.first
|
|
35
|
+
expected = [mark[:content_column], paragraph_content_column(paragraph, first_line)].max
|
|
36
|
+
lines.drop(1).each do |number|
|
|
37
|
+
next if multiline_atom_line?(number)
|
|
38
|
+
|
|
39
|
+
actual = paragraph_content_column(paragraph, number)
|
|
40
|
+
next unless actual < expected
|
|
41
|
+
next if document.line(number)[actual..].empty?
|
|
42
|
+
|
|
43
|
+
range = document.range(number, actual, number, actual + 1)
|
|
44
|
+
prose_offense(range, message: "Indent list continuation text to column #{expected}.")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def paragraph_content_column(paragraph, number)
|
|
49
|
+
source_line = paragraph.options[:prose_source].split("\n", -1).fetch(number - paragraph.options[:location])
|
|
50
|
+
document.content_start(paragraph, number) + source_line[/\A[ \t]*/].length
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def multiline_atom_line?(number)
|
|
54
|
+
document.multiline_atoms.any? { |atom| number.between?(atom.line, atom.last_line) }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/list_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class ListIndentation < Base
|
|
9
|
+
include RuboCop::Prose::ListRules
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def investigate_document
|
|
14
|
+
list_groups do |_list, marks, _parent|
|
|
15
|
+
expected = marks.first[:indent]
|
|
16
|
+
marks.drop(1).each do |mark|
|
|
17
|
+
next if mark[:indent] == expected
|
|
18
|
+
|
|
19
|
+
prose_offense(document.range(mark[:line], mark[:column], mark[:line],
|
|
20
|
+
mark[:column] + mark[:marker].length),
|
|
21
|
+
message: "Align sibling list markers at indentation #{expected}.")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../prose/list_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Markdown
|
|
8
|
+
class ListMarkerSpacing < 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
|
+
multiline = marks.any? { |mark| node_last_line(mark[:item]) > mark[:line] }
|
|
17
|
+
key = "#{list.type == :ul ? 'Unordered' : 'Ordered'}#{multiline ? 'Multi' : 'Single'}"
|
|
18
|
+
spaces = cop_config.fetch(key, 1)
|
|
19
|
+
marks.each do |mark|
|
|
20
|
+
next if mark[:gap].empty? || mark[:gap] == ' ' * spaces
|
|
21
|
+
|
|
22
|
+
first = mark[:column] + mark[:marker].length
|
|
23
|
+
range = document.range(mark[:line], first, mark[:line], mark[:content_column])
|
|
24
|
+
prose_offense(range, message: "Use #{spaces} spaces after this list marker.") do |corrector|
|
|
25
|
+
next unless spacing_correction_safe?(mark, spaces)
|
|
26
|
+
|
|
27
|
+
corrector.replace(processed_range(range), ' ' * spaces)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def spacing_correction_safe?(mark, spaces)
|
|
34
|
+
item = mark[:item]
|
|
35
|
+
return false unless node_last_line(item) == mark[:line]
|
|
36
|
+
return false unless item.children.all? { |child| child.type == :p }
|
|
37
|
+
|
|
38
|
+
content = document.line(mark[:line])[mark[:content_column]..]
|
|
39
|
+
spaces.between?(1, 4) && !content.match?(/\A(?:[-+*]|\d+[.)]|>|`{3,}|~{3,}| {4})/)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|