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,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Prose
|
|
5
|
+
class MarkdownDocument < Document
|
|
6
|
+
FRONT_MATTER = /\A(?:\uFEFF)?---[ \t]*\r?\n.*?^(?:---|\.\.\.)[ \t]*(?:\r?\n|\z)/m
|
|
7
|
+
BLOCK_START = /\A(?:[>#]|[-+*](?:\s|\z)|\d+[.)](?:\s|\z)|`{3}|~{3}|[=-]+[\t ]*\z)/
|
|
8
|
+
|
|
9
|
+
attr_reader :front_matter
|
|
10
|
+
|
|
11
|
+
def self.file?(path, configuration = nil)
|
|
12
|
+
Document.format(path, configuration) == :markdown
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(buffer, ignore_front_matter: true, configuration: {})
|
|
16
|
+
ignore_front_matter = configuration.fetch('Markdown', {}).fetch('IgnoreFrontMatter', ignore_front_matter)
|
|
17
|
+
unless [true, false].include?(ignore_front_matter)
|
|
18
|
+
raise ValidationError, 'Markdown: IgnoreFrontMatter must be true or false.'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
super(buffer)
|
|
22
|
+
@front_matter = source[FRONT_MATTER]
|
|
23
|
+
@ignore_front_matter = ignore_front_matter
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def root
|
|
27
|
+
parsed.first
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def warnings
|
|
31
|
+
parsed.last
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def nodes(type)
|
|
35
|
+
@nodes ||= walk(root).group_by(&:type)
|
|
36
|
+
@nodes.fetch(type, [])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def paragraphs
|
|
40
|
+
@paragraphs ||= nodes(:p).select { |node| node.options[:prose_source] }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def hard_breaks
|
|
44
|
+
@hard_breaks ||= paragraphs.flat_map { |paragraph| find_hard_breaks(paragraph) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def link_rules
|
|
48
|
+
@link_rules ||= LinkRules.new(self)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def emphasis_ranges(type)
|
|
52
|
+
@emphasis_ranges ||= InlineRules.emphasis(self)
|
|
53
|
+
@emphasis_ranges.fetch(type, [])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def multiline_atoms
|
|
57
|
+
@multiline_atoms ||= paragraphs.flat_map { |paragraph| atomic_ranges(paragraph) }
|
|
58
|
+
.reject { |range| range.line == range.last_line }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def headings
|
|
62
|
+
nodes(:header).map { |node| [node.options[:level], plain_text(node), node.options[:location]] }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def plain_text(node)
|
|
66
|
+
return node.value.to_s if %i[text codespan].include?(node.type)
|
|
67
|
+
return node.value.char if node.type == :entity
|
|
68
|
+
|
|
69
|
+
node.children.map { |child| plain_text(child) }.join
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def front_matter_line?(number)
|
|
73
|
+
@ignore_front_matter && front_matter && number <= front_matter.lines.length
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def code_line?(number)
|
|
77
|
+
@code_lines ||= nodes(:codeblock).flat_map do |node|
|
|
78
|
+
first = node.options.fetch(:location)
|
|
79
|
+
length = node.value.lines.length + (node.options[:fenced] ? 2 : 0)
|
|
80
|
+
(first...(first + length)).to_a
|
|
81
|
+
end.to_set
|
|
82
|
+
@code_lines.include?(number)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def ignored_line?(number)
|
|
86
|
+
code_line?(number) || front_matter_line?(number)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def breakable_space?(text, first, last)
|
|
90
|
+
!text[last..].match?(BLOCK_START) && text[0...first][/\\+\z/].to_s.length.even?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def prose_lines
|
|
94
|
+
return enum_for(__method__) unless block_given?
|
|
95
|
+
|
|
96
|
+
lines.each_with_index do |text, index|
|
|
97
|
+
number = index + 1
|
|
98
|
+
yield text, number unless code_line?(number) || front_matter_line?(number)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def content_start(paragraph, number)
|
|
103
|
+
text = source_lines(paragraph).fetch(number - paragraph.options[:location])
|
|
104
|
+
line(number).rindex(text) || raise("Cannot map Markdown paragraph on line #{number}")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def line_numbers(paragraph)
|
|
108
|
+
first = paragraph.options[:location]
|
|
109
|
+
first...(first + source_lines(paragraph).length)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def continuation_prefix(paragraph)
|
|
113
|
+
number = paragraph.options[:location]
|
|
114
|
+
prefix = line(number)[0...content_start(paragraph, number)]
|
|
115
|
+
prefix.gsub(/[-+*]|\d+[.)]/) { |marker| ' ' * marker.length }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def soft_breaks(paragraph)
|
|
119
|
+
line_numbers(paragraph).to_a[0...-1].reject do |number|
|
|
120
|
+
text = line(number)
|
|
121
|
+
join = range(number, text.rstrip.length, number + 1, content_start(paragraph, number + 1))
|
|
122
|
+
following = line(number + 1)[content_start(paragraph, number + 1)..]
|
|
123
|
+
text.end_with?(' ') || text[/\\+\z/].to_s.length.odd? ||
|
|
124
|
+
text.match?(/<br\b[^>]*>\s*\z/i) || following.match?(/\A<br\b[^>]*>/i) ||
|
|
125
|
+
atomic_ranges(paragraph).any? { |atom| atom.overlaps?(join) }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def atomic_ranges(paragraph)
|
|
130
|
+
paragraph.options[:original_ranges] ||= begin
|
|
131
|
+
inline = ::Parser::Source::Buffer.new('(paragraph)', source: paragraph.options[:prose_source])
|
|
132
|
+
paragraph.options[:prose_atoms].map do |first, last|
|
|
133
|
+
start_line, start_column = inline.decompose_position(first)
|
|
134
|
+
end_line, end_column = inline.decompose_position(last)
|
|
135
|
+
first_line = paragraph.options[:location] + start_line - 1
|
|
136
|
+
last_line = paragraph.options[:location] + end_line - 1
|
|
137
|
+
range(first_line, content_start(paragraph, first_line) + start_column,
|
|
138
|
+
last_line, content_start(paragraph, last_line) + end_column)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
def source_lines(paragraph)
|
|
146
|
+
paragraph.options[:prose_lines] ||= paragraph.options[:prose_source].split("\n", -1)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def parsed
|
|
150
|
+
@parsed ||= begin
|
|
151
|
+
text = @ignore_front_matter ? source.sub(FRONT_MATTER) { |header| header.gsub(/[^\r\n]/, ' ') } : source
|
|
152
|
+
Parser.parse(text, hard_wrap: false, gfm_quirks: %i[paragraph_end no_auto_typographic])
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def walk(node)
|
|
157
|
+
[node] + node.children.flat_map { |child| walk(child) }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def find_hard_breaks(node)
|
|
161
|
+
node.children.flat_map do |child|
|
|
162
|
+
child.type == :br ? [child] : find_hard_breaks(child)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kramdown'
|
|
4
|
+
require 'kramdown-parser-gfm'
|
|
5
|
+
|
|
6
|
+
module RuboCop
|
|
7
|
+
module Prose
|
|
8
|
+
class Parser < Kramdown::Parser::GFM
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
def parse_codeblock_fenced
|
|
12
|
+
result = super
|
|
13
|
+
if result
|
|
14
|
+
block = @tree.children.last
|
|
15
|
+
block.options[:fenced] = true
|
|
16
|
+
block.options[:code_start_line] = block.options[:location] + 1
|
|
17
|
+
end
|
|
18
|
+
result
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parse_codeblock
|
|
22
|
+
result = super
|
|
23
|
+
@tree.children.last.options[:code_start_line] = @tree.children.last.options[:location] if result
|
|
24
|
+
result
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def update_tree(element)
|
|
28
|
+
if element.type == :p && (raw = element.children.find { |child| child.type == :raw_text })
|
|
29
|
+
raw.value.sub!(/\n+\z/, '')
|
|
30
|
+
element.options[:prose_source] = raw.value.dup
|
|
31
|
+
element.options[:prose_atoms] = raw.options[:prose_atoms] = []
|
|
32
|
+
raw.options[:prose_atoms] << [0, 3] if raw.value.match?(/\A\[[ xX]\]/)
|
|
33
|
+
end
|
|
34
|
+
result = super
|
|
35
|
+
if element.type == :p && element.children.all? do |child|
|
|
36
|
+
child.type == :html_element || (child.type == :text && child.value.strip.empty?)
|
|
37
|
+
end
|
|
38
|
+
element.options.delete(:prose_source)
|
|
39
|
+
end
|
|
40
|
+
result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def parse_spans(element, *args, &)
|
|
44
|
+
previous = @prose_atoms
|
|
45
|
+
@prose_atoms = element.options[:prose_atoms] if element.type == :raw_text
|
|
46
|
+
super
|
|
47
|
+
ensure
|
|
48
|
+
@prose_atoms = previous
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_list
|
|
52
|
+
Kramdown::Parser::Kramdown.instance_method(:parse_list).bind_call(self)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def parse_link
|
|
56
|
+
if @src.pos.zero? && @src.check(/\[[ xX]\]/)
|
|
57
|
+
add_text(@src.scan(/\[[ xX]\]/))
|
|
58
|
+
else
|
|
59
|
+
capture_atom { super }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
%i[parse_codespan parse_autolink parse_span_html].each do |method|
|
|
64
|
+
define_method(method) { capture_atom { super() } }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def capture_atom
|
|
68
|
+
scanner = @src
|
|
69
|
+
first = scanner.pos
|
|
70
|
+
atoms = @prose_atoms
|
|
71
|
+
result = yield
|
|
72
|
+
if atoms
|
|
73
|
+
atoms << [scanner.string.byteslice(0, first).length,
|
|
74
|
+
scanner.string.byteslice(0, scanner.pos).length]
|
|
75
|
+
end
|
|
76
|
+
result
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lint_roller'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
module RuboCop
|
|
7
|
+
module Prose
|
|
8
|
+
class Plugin < LintRoller::Plugin
|
|
9
|
+
def about
|
|
10
|
+
LintRoller::About.new(
|
|
11
|
+
name: 'rubocop-prose', version: VERSION,
|
|
12
|
+
homepage: 'https://github.com/dblock/rubocop-prose',
|
|
13
|
+
description: 'Lint prose in Markdown and plain-text documents.'
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def supported?(context)
|
|
18
|
+
context.engine == :rubocop
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def rules(_context)
|
|
22
|
+
@configuration ||= Tempfile.new(['rubocop-prose', '.yml']).tap do |file|
|
|
23
|
+
file.write(Prose.default_configuration.to_yaml)
|
|
24
|
+
file.flush
|
|
25
|
+
end
|
|
26
|
+
LintRoller::Rules.new(type: :path, config_format: :rubocop, value: Pathname.new(@configuration.path))
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Prose
|
|
5
|
+
module Runner
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def get_processed_source(file, *arguments, **keywords)
|
|
9
|
+
configuration = @config_store.for_file(file)
|
|
10
|
+
format = configuration.prose_file_types.format(file)
|
|
11
|
+
return super if format == Document.format(file)
|
|
12
|
+
|
|
13
|
+
# The native factory normally initializes this optional preprocessor context.
|
|
14
|
+
if defined?(RuboCop::Markdown) && RuboCop::Markdown.respond_to?(:config_store=)
|
|
15
|
+
RuboCop::Markdown.config_store = @config_store
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
source = if keywords[:source]
|
|
19
|
+
RuboCop::Util.emulate_write_read_cycle(keywords[:source])
|
|
20
|
+
else
|
|
21
|
+
prose_source(file)
|
|
22
|
+
end
|
|
23
|
+
processed = ProcessedSource.new(
|
|
24
|
+
source, configuration.target_ruby_version, file,
|
|
25
|
+
parser_engine: configuration.parser_engine, prose_configuration: configuration
|
|
26
|
+
)
|
|
27
|
+
processed.config = configuration
|
|
28
|
+
processed.registry = mobilized_cop_classes(configuration)
|
|
29
|
+
processed
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def prose_source(file)
|
|
33
|
+
@options[:stdin] || File.binread(file)
|
|
34
|
+
rescue Errno::ENOENT
|
|
35
|
+
raise RuboCop::Error, "No such file or directory: #{file}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module TargetFinder
|
|
40
|
+
def find(...)
|
|
41
|
+
@config_store.for_pwd.prose_file_types
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def ruby_file?(file, ...)
|
|
48
|
+
types = @config_store.for_file(file).prose_file_types
|
|
49
|
+
super || !types.format(file).nil?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_inspect?(file, ...)
|
|
53
|
+
configuration = @config_store.for_file(file)
|
|
54
|
+
return false if configuration.prose_file_types.format(file) && configuration.file_to_exclude?(file)
|
|
55
|
+
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
RuboCop::Runner.prepend(RuboCop::Prose::Runner)
|
|
63
|
+
RuboCop::TargetFinder.prepend(RuboCop::Prose::TargetFinder)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Prose
|
|
5
|
+
module Source
|
|
6
|
+
DIRECTIVE = %r{(?:<!--|<span\b[^>]*>)\s*(#\s*rubocop:[^\n]*?)\s*(?:-->|</span>)}
|
|
7
|
+
|
|
8
|
+
module MarkdownMapping
|
|
9
|
+
def markdown_document
|
|
10
|
+
super unless prose_format == :text
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module Parsing
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def parse(source, *)
|
|
18
|
+
return super unless prose_format
|
|
19
|
+
|
|
20
|
+
if prose_format == :markdown && respond_to?(:markdown_document)
|
|
21
|
+
super
|
|
22
|
+
return if prose_markdown_document
|
|
23
|
+
|
|
24
|
+
@diagnostics.clear
|
|
25
|
+
@parser_error = @tokens = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
super('', *)
|
|
29
|
+
@buffer = ::Parser::Source::Buffer.new(path, source: raw_source)
|
|
30
|
+
@prose_documents = @prose_comments = nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
attr_reader :prose_format
|
|
35
|
+
|
|
36
|
+
def initialize(source, ruby_version, path = nil, prose_configuration: nil, **)
|
|
37
|
+
@prose_format = Document.format(path, prose_configuration)
|
|
38
|
+
extend Parsing if prose_format
|
|
39
|
+
extend MarkdownMapping if prose_format == :text && respond_to?(:markdown_document)
|
|
40
|
+
super(source, ruby_version, path, **)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def prose_document(configuration: nil)
|
|
44
|
+
@prose_documents ||= {}.compare_by_identity
|
|
45
|
+
@prose_documents[configuration] ||= Document.build(
|
|
46
|
+
path,
|
|
47
|
+
prose_markdown_document ? prose_markdown_document.buffer : buffer,
|
|
48
|
+
configuration: configuration, format: prose_format
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def valid_syntax?
|
|
53
|
+
prose_format && !prose_markdown_document ? false : super
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def comments
|
|
57
|
+
return [] if prose_format == :text
|
|
58
|
+
return super unless prose_format && !prose_markdown_document
|
|
59
|
+
|
|
60
|
+
prose_comments
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def prose_markdown_document
|
|
64
|
+
markdown_document if prose_format == :markdown && respond_to?(:markdown_document)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def prose_comments
|
|
70
|
+
@prose_comments ||= buffer.source.to_enum(:scan, DIRECTIVE).filter_map do
|
|
71
|
+
match = Regexp.last_match
|
|
72
|
+
range = ::Parser::Source::Range.new(buffer, match.begin(1), match.end(1))
|
|
73
|
+
next if prose_document.code_line?(range.first_line) || prose_document.front_matter_line?(range.first_line)
|
|
74
|
+
|
|
75
|
+
::Parser::Source::Comment.new(range)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
RuboCop::ProcessedSource.prepend(RuboCop::Prose::Source)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'heading_rules'
|
|
4
|
+
require_relative 'code_rules'
|
|
5
|
+
require_relative 'list_rules'
|
|
6
|
+
require_relative 'table_rules'
|
|
7
|
+
|
|
8
|
+
module RuboCop
|
|
9
|
+
module Prose
|
|
10
|
+
class StructuralBlankLines
|
|
11
|
+
include HeadingRules
|
|
12
|
+
include CodeRules
|
|
13
|
+
include ListRules
|
|
14
|
+
|
|
15
|
+
def initialize(document, configuration, processed_source)
|
|
16
|
+
@document = document
|
|
17
|
+
@configuration = configuration
|
|
18
|
+
@processed_source = processed_source
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def protected_lines
|
|
22
|
+
@protected_lines = Set.new
|
|
23
|
+
heading_boundaries if enabled?(Cop::Markdown::HeadingBlankLines)
|
|
24
|
+
fence_boundaries if enabled?(Cop::Markdown::FenceBlankLines)
|
|
25
|
+
list_boundaries if enabled?(Cop::Markdown::ListBlankLines)
|
|
26
|
+
table_boundaries if enabled?(Cop::Markdown::TableBlankLines)
|
|
27
|
+
@protected_lines
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :document
|
|
33
|
+
|
|
34
|
+
def enabled?(cop)
|
|
35
|
+
@configuration.cop_enabled?(cop.cop_name) &&
|
|
36
|
+
cop.new(@configuration).relevant_file?(@processed_source.file_path)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def heading_boundaries
|
|
40
|
+
settings = @configuration.for_cop('Markdown/HeadingBlankLines')
|
|
41
|
+
heading_entries.each do |heading|
|
|
42
|
+
protect(heading[:line] - 1, -1, settings.fetch('LinesAbove', 1), blank: method(:heading_blank?))
|
|
43
|
+
protect(heading[:last_line] + 1, 1, settings.fetch('LinesBelow', 1), blank: method(:heading_blank?))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def fence_boundaries
|
|
48
|
+
settings = @configuration.for_cop('Markdown/FenceBlankLines')
|
|
49
|
+
fenced_blocks(list_items: settings.fetch('ListItems', true)) do |details|
|
|
50
|
+
unless details[:opening][:prefix].match?(/[-+*]|\d+[.)]/)
|
|
51
|
+
protect(details[:first] - 1, -1, settings.fetch('LinesAbove', 1))
|
|
52
|
+
end
|
|
53
|
+
protect(details[:last] + 1, 1, settings.fetch('LinesBelow', 1))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def list_boundaries
|
|
58
|
+
list_groups do |list, marks, parent|
|
|
59
|
+
next if parent
|
|
60
|
+
|
|
61
|
+
protect(marks.first[:line] - 1, -1, 1)
|
|
62
|
+
protect(node_last_line(list) + 1, 1, 1)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def table_boundaries
|
|
67
|
+
TableRules.tables(document).each do |table|
|
|
68
|
+
protect(table.rows.first.number - 1, -1, 1, front_matter: false)
|
|
69
|
+
protect(table.rows.last.number + 1, 1, 1)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def protect(number, direction, required, blank: method(:container_blank?), front_matter: true)
|
|
74
|
+
return if required.zero?
|
|
75
|
+
|
|
76
|
+
blanks = []
|
|
77
|
+
while number.between?(1, document.lines.length)
|
|
78
|
+
return if front_matter && direction.negative? && document.front_matter_line?(number)
|
|
79
|
+
break unless blank.call(number)
|
|
80
|
+
|
|
81
|
+
blanks << number
|
|
82
|
+
number += direction
|
|
83
|
+
end
|
|
84
|
+
return unless number.between?(1, document.lines.length)
|
|
85
|
+
|
|
86
|
+
@protected_lines.merge(blanks.sort.first(required))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'inline_rules'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Prose
|
|
7
|
+
module TableRules
|
|
8
|
+
Row = Struct.new(:number, :text, :begin_pos, :end_pos, :pipes, :cells, :leading, :trailing, keyword_init: true)
|
|
9
|
+
Cell = Struct.new(:begin_pos, :end_pos, :text, keyword_init: true)
|
|
10
|
+
Table = Struct.new(:rows, keyword_init: true)
|
|
11
|
+
|
|
12
|
+
def self.row(document, number)
|
|
13
|
+
return if number > document.lines.length || document.code_line?(number) || document.front_matter_line?(number)
|
|
14
|
+
|
|
15
|
+
text = document.line(number)
|
|
16
|
+
first = text[/\A[ \t]*(?:>[ \t]*)*/].length
|
|
17
|
+
last = text.rstrip.length
|
|
18
|
+
return if first >= last
|
|
19
|
+
|
|
20
|
+
protected = InlineRules.spans(text, links: false, html: false)
|
|
21
|
+
pipes = (first...last).select do |index|
|
|
22
|
+
text[index] == '|' && protected.none? { |span| index >= span.begin_pos && index < span.end_pos }
|
|
23
|
+
end
|
|
24
|
+
leading = pipes.first == first
|
|
25
|
+
trailing = pipes.last == last - 1
|
|
26
|
+
boundaries = pipes.dup
|
|
27
|
+
boundaries.shift if leading
|
|
28
|
+
boundaries.pop if trailing
|
|
29
|
+
start = leading ? first + 1 : first
|
|
30
|
+
finish = trailing ? last - 1 : last
|
|
31
|
+
cells = []
|
|
32
|
+
boundaries.each do |position|
|
|
33
|
+
cells << Cell.new(begin_pos: start, end_pos: position, text: text[start...position])
|
|
34
|
+
start = position + 1
|
|
35
|
+
end
|
|
36
|
+
cells << Cell.new(begin_pos: start, end_pos: finish, text: text[start...finish])
|
|
37
|
+
Row.new(number: number, text: text, begin_pos: first, end_pos: last, pipes: pipes, cells: cells,
|
|
38
|
+
leading: leading, trailing: trailing)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.tables(document)
|
|
42
|
+
# A malformed body can make GFM reject the entire table. Its header and delimiter
|
|
43
|
+
# still identify the intended table, but only inside parser-recognized prose.
|
|
44
|
+
eligible = InlineRules.lines(document).to_set { |_, number, _| number }
|
|
45
|
+
result = []
|
|
46
|
+
number = 1
|
|
47
|
+
while number < document.lines.length
|
|
48
|
+
header = row(document, number) if eligible.include?(number)
|
|
49
|
+
separator = row(document, number + 1) if header && !header.pipes.empty?
|
|
50
|
+
if separator&.cells&.all? { |cell| cell.text.strip.match?(/\A:?-{3,}:?\z/) }
|
|
51
|
+
rows = [header, separator]
|
|
52
|
+
cursor = number + 2
|
|
53
|
+
while cursor <= document.lines.length && eligible.include?(cursor)
|
|
54
|
+
body = row(document, cursor)
|
|
55
|
+
break unless body && !body.pipes.empty?
|
|
56
|
+
|
|
57
|
+
rows << body
|
|
58
|
+
cursor += 1
|
|
59
|
+
end
|
|
60
|
+
result << Table.new(rows: rows)
|
|
61
|
+
number = cursor
|
|
62
|
+
else
|
|
63
|
+
number += 1
|
|
64
|
+
end
|
|
65
|
+
header = separator = nil
|
|
66
|
+
end
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Prose
|
|
5
|
+
class TextDocument < Document
|
|
6
|
+
Paragraph = Struct.new(:first_line, :last_line)
|
|
7
|
+
|
|
8
|
+
def self.file?(path, configuration = nil)
|
|
9
|
+
Document.format(path, configuration) == :text
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def paragraphs
|
|
13
|
+
@paragraphs ||= begin
|
|
14
|
+
result = []
|
|
15
|
+
lines.each_with_index do |text, index|
|
|
16
|
+
next if text.strip.empty?
|
|
17
|
+
|
|
18
|
+
number = index + 1
|
|
19
|
+
if result.last && result.last.last_line == number - 1
|
|
20
|
+
result.last.last_line = number
|
|
21
|
+
else
|
|
22
|
+
result << Paragraph.new(number, number)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
result
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def line_numbers(paragraph)
|
|
30
|
+
paragraph.first_line..paragraph.last_line
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def content_start(_paragraph, _number)
|
|
34
|
+
0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def continuation_prefix(_paragraph)
|
|
38
|
+
''
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def atomic_ranges(_paragraph)
|
|
42
|
+
[]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def soft_breaks(paragraph)
|
|
46
|
+
(paragraph.first_line...paragraph.last_line).to_a
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reflowable?(paragraph)
|
|
50
|
+
line_numbers(paragraph).none? { |number| line(number).match?(/\A[ \t]/) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rubocop'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require_relative 'rubocop/prose/version'
|
|
6
|
+
require_relative 'rubocop/prose/parser'
|
|
7
|
+
require_relative 'rubocop/prose/file_types'
|
|
8
|
+
require_relative 'rubocop/prose/document'
|
|
9
|
+
require_relative 'rubocop/prose/markdown_document'
|
|
10
|
+
require_relative 'rubocop/prose/text_document'
|
|
11
|
+
require_relative 'rubocop/prose/source'
|
|
12
|
+
require_relative 'rubocop/prose/runner'
|
|
13
|
+
require_relative 'rubocop/prose/plugin'
|
|
14
|
+
require_relative 'rubocop/cop/prose/base'
|
|
15
|
+
require_relative 'rubocop/cop/markdown/base'
|
|
16
|
+
require_relative 'rubocop/cop/text/base'
|
|
17
|
+
|
|
18
|
+
Dir[File.join(__dir__, 'rubocop/cop/{prose,markdown,text}/*.rb')].each { |path| require path }
|
|
19
|
+
|
|
20
|
+
module RuboCop
|
|
21
|
+
module Prose
|
|
22
|
+
def self.default_configuration
|
|
23
|
+
paths = Dir[File.expand_path('../config/default.yml', __dir__),
|
|
24
|
+
File.expand_path('../config/cops/**/*.yml', __dir__)]
|
|
25
|
+
paths.each_with_object({}) do |path, config|
|
|
26
|
+
config.merge!(YAML.safe_load_file(path)) { |_, previous, current| previous.merge(current) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|