mdl 0.12.0 → 0.13.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 +4 -4
- data/lib/mdl/cli.rb +6 -0
- data/lib/mdl/doc.rb +24 -0
- data/lib/mdl/formatters/sarif.rb +89 -0
- data/lib/mdl/rules.rb +103 -5
- data/lib/mdl/ruleset.rb +61 -0
- data/lib/mdl/style.rb +1 -1
- data/lib/mdl/version.rb +1 -1
- data/lib/mdl.rb +9 -5
- data/mdl.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb98b688ab9d9dc7bac3c169f20fefc7c0cb27ce5bf574bec52053d592b7a9c7
|
4
|
+
data.tar.gz: 873df51b4011d23617c1d73ab47a1928765fee0502b6f58853e2932bb3d5ec59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fea533cf2d4a61c4da291a094e3f94080acee8837919935e789e1b39c98d8bcc416cb9fcb143a10a1f63738ee993d3fe5ce89ca3dc664a9a11712796db8dbc4d
|
7
|
+
data.tar.gz: 8dc7470f981b1267dddb1f668afc4f85c742d6c467ae9a31bcaf24618792d6d9c7792a328ff3258d8efd071d9592b4a84600a2825e787b26d1cb5f7e979989af
|
data/lib/mdl/cli.rb
CHANGED
@@ -107,6 +107,12 @@ module MarkdownLint
|
|
107
107
|
:description => 'JSON output',
|
108
108
|
:boolean => true
|
109
109
|
|
110
|
+
option :sarif,
|
111
|
+
:short => '-S',
|
112
|
+
:long => '--sarif',
|
113
|
+
:description => 'SARIF output',
|
114
|
+
:boolean => true
|
115
|
+
|
110
116
|
def run(argv = ARGV)
|
111
117
|
parse_options(argv)
|
112
118
|
|
data/lib/mdl/doc.rb
CHANGED
@@ -268,6 +268,30 @@ module MarkdownLint
|
|
268
268
|
lines
|
269
269
|
end
|
270
270
|
|
271
|
+
##
|
272
|
+
# Returns the element as plaintext
|
273
|
+
|
274
|
+
def extract_as_text(element)
|
275
|
+
quotes = {
|
276
|
+
:rdquo => '"',
|
277
|
+
:ldquo => '"',
|
278
|
+
:lsquo => "'",
|
279
|
+
:rsquo => "'",
|
280
|
+
}
|
281
|
+
# If anything goes amiss here, e.g. unknown type, then nil will be
|
282
|
+
# returned and we'll just not catch that part of the text, which seems
|
283
|
+
# like a sensible failure mode.
|
284
|
+
element.children.map do |e|
|
285
|
+
if e.type == :text || e.type == :codespan
|
286
|
+
e.value
|
287
|
+
elsif %i{strong em p a}.include?(e.type)
|
288
|
+
extract_as_text(e).join("\n")
|
289
|
+
elsif e.type == :smart_quote
|
290
|
+
quotes[e.value]
|
291
|
+
end
|
292
|
+
end.join.split("\n")
|
293
|
+
end
|
294
|
+
|
271
295
|
private
|
272
296
|
|
273
297
|
##
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module MarkdownLint
|
4
|
+
# SARIF formatter
|
5
|
+
#
|
6
|
+
# @see https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
|
7
|
+
class SarifFormatter
|
8
|
+
class << self
|
9
|
+
def generate(rules, results)
|
10
|
+
matched_rules_id = results.map { |result| result['rule'] }.uniq
|
11
|
+
matched_rules = rules.select { |id, _| matched_rules_id.include?(id) }
|
12
|
+
JSON.generate(generate_sarif(matched_rules, results))
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_sarif(rules, results)
|
16
|
+
{
|
17
|
+
:'$schema' => 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
|
18
|
+
:version => '2.1.0',
|
19
|
+
:runs => [
|
20
|
+
{
|
21
|
+
:tool => {
|
22
|
+
:driver => {
|
23
|
+
:name => 'Markdown lint',
|
24
|
+
:version => MarkdownLint::VERSION,
|
25
|
+
:informationUri => 'https://github.com/markdownlint/markdownlint',
|
26
|
+
:rules => generate_sarif_rules(rules),
|
27
|
+
},
|
28
|
+
},
|
29
|
+
:results => generate_sarif_results(rules, results),
|
30
|
+
}
|
31
|
+
],
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_sarif_rules(rules)
|
36
|
+
rules.map do |id, rule|
|
37
|
+
{
|
38
|
+
:id => id,
|
39
|
+
:name => rule.aliases.first.split('-').map(&:capitalize).join,
|
40
|
+
:defaultConfiguration => {
|
41
|
+
:level => 'note',
|
42
|
+
},
|
43
|
+
:properties => {
|
44
|
+
:description => rule.description,
|
45
|
+
:tags => rule.tags,
|
46
|
+
:queryURI => rule.docs_url,
|
47
|
+
},
|
48
|
+
:shortDescription => {
|
49
|
+
:text => rule.description,
|
50
|
+
},
|
51
|
+
:fullDescription => {
|
52
|
+
:text => rule.description,
|
53
|
+
},
|
54
|
+
:helpUri => rule.docs_url,
|
55
|
+
:help => {
|
56
|
+
:text => "More info: #{rule.docs_url}",
|
57
|
+
:markdown => "[More info](#{rule.docs_url})",
|
58
|
+
},
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def generate_sarif_results(rules, results)
|
64
|
+
results.map do |result|
|
65
|
+
{
|
66
|
+
:ruleId => result['rule'],
|
67
|
+
:ruleIndex => rules.find_index { |id, _| id == result['rule'] },
|
68
|
+
:message => {
|
69
|
+
:text => "#{result['rule']} - #{result['description']}",
|
70
|
+
},
|
71
|
+
:locations => [
|
72
|
+
{
|
73
|
+
:physicalLocation => {
|
74
|
+
:artifactLocation => {
|
75
|
+
:uri => result['filename'],
|
76
|
+
:uriBaseId => '%SRCROOT%',
|
77
|
+
},
|
78
|
+
:region => {
|
79
|
+
:startLine => result['line'],
|
80
|
+
},
|
81
|
+
},
|
82
|
+
}
|
83
|
+
],
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/mdl/rules.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
docs do |id, description|
|
2
2
|
url_hash = [id.downcase,
|
3
3
|
description.downcase.gsub(/[^a-z]+/, '-')].join('---')
|
4
|
-
"https://github.com/markdownlint/markdownlint/blob/
|
4
|
+
"https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md##{url_hash}"
|
5
5
|
end
|
6
6
|
|
7
7
|
rule 'MD001', 'Header levels should only increment by one level at a time' do
|
@@ -33,7 +33,7 @@ end
|
|
33
33
|
|
34
34
|
rule 'MD003', 'Header style' do
|
35
35
|
# Header styles are things like ### and adding underscores
|
36
|
-
# See
|
36
|
+
# See https://daringfireball.net/projects/markdown/syntax#header
|
37
37
|
tags :headers
|
38
38
|
aliases 'header-style'
|
39
39
|
# :style can be one of :consistent, :atx, :atx_closed, :setext
|
@@ -126,7 +126,7 @@ rule 'MD005', 'Inconsistent indentation for list items at the same level' do
|
|
126
126
|
end
|
127
127
|
|
128
128
|
rule 'MD006', 'Consider starting bulleted lists at the beginning of the line' do
|
129
|
-
# Starting at the beginning of the line means that
|
129
|
+
# Starting at the beginning of the line means that indentation for each
|
130
130
|
# bullet level can be identical.
|
131
131
|
tags :bullet, :ul, :indentation
|
132
132
|
aliases 'ul-start-left'
|
@@ -161,7 +161,7 @@ end
|
|
161
161
|
rule 'MD009', 'Trailing spaces' do
|
162
162
|
tags :whitespace
|
163
163
|
aliases 'no-trailing-spaces'
|
164
|
-
params :br_spaces =>
|
164
|
+
params :br_spaces => 2
|
165
165
|
check do |doc|
|
166
166
|
errors = doc.matching_lines(/\s$/)
|
167
167
|
if params[:br_spaces] > 1
|
@@ -458,7 +458,10 @@ rule 'MD027', 'Multiple spaces after blockquote symbol' do
|
|
458
458
|
errors = []
|
459
459
|
doc.find_type_elements(:blockquote).each do |e|
|
460
460
|
linenum = doc.element_linenumber(e)
|
461
|
-
lines = doc.
|
461
|
+
lines = doc.extract_as_text(e)
|
462
|
+
# Handle first line specially as whitespace is stripped from the text
|
463
|
+
# element
|
464
|
+
errors << linenum if doc.element_line(e).match(/^\s*> /)
|
462
465
|
lines.each do |line|
|
463
466
|
errors << linenum if line.start_with?(' ')
|
464
467
|
linenum += 1
|
@@ -619,8 +622,14 @@ end
|
|
619
622
|
rule 'MD033', 'Inline HTML' do
|
620
623
|
tags :html
|
621
624
|
aliases 'no-inline-html'
|
625
|
+
params :allowed_elements => ''
|
622
626
|
check do |doc|
|
623
627
|
doc.element_linenumbers(doc.find_type(:html_element))
|
628
|
+
allowed = params[:allowed_elements].delete(" \t\r\n").downcase.split(',')
|
629
|
+
errors = doc.find_type_elements(:html_element).reject do |e|
|
630
|
+
allowed.include?(e.value)
|
631
|
+
end
|
632
|
+
doc.element_linenumbers(errors)
|
624
633
|
end
|
625
634
|
end
|
626
635
|
|
@@ -790,3 +799,92 @@ rule 'MD047', 'File should end with a single newline character' do
|
|
790
799
|
error_lines
|
791
800
|
end
|
792
801
|
end
|
802
|
+
|
803
|
+
rule 'MD055', 'Table row doesn\'t begin/end with pipes' do
|
804
|
+
tags :tables
|
805
|
+
aliases 'table-rows-start-and-end-with-pipes'
|
806
|
+
check do |doc|
|
807
|
+
error_lines = []
|
808
|
+
tables = doc.find_type_elements(:table)
|
809
|
+
lines = doc.lines
|
810
|
+
|
811
|
+
tables.each do |table|
|
812
|
+
table_pos = table.options[:location] - 1
|
813
|
+
table_rows = get_table_rows(lines, table_pos)
|
814
|
+
|
815
|
+
table_rows.each_with_index do |line, index|
|
816
|
+
if line.length < 2 || line[0] != '|' || line[-1] != '|'
|
817
|
+
error_lines << (table_pos + index + 1)
|
818
|
+
end
|
819
|
+
end
|
820
|
+
end
|
821
|
+
|
822
|
+
error_lines
|
823
|
+
end
|
824
|
+
end
|
825
|
+
|
826
|
+
rule 'MD056', 'Table has inconsistent number of columns' do
|
827
|
+
tags :tables
|
828
|
+
aliases 'inconsistent-columns-in-table'
|
829
|
+
check do |doc|
|
830
|
+
error_lines = []
|
831
|
+
tables = doc.find_type_elements(:table)
|
832
|
+
lines = doc.lines
|
833
|
+
|
834
|
+
tables.each do |table|
|
835
|
+
table_pos = table.options[:location] - 1
|
836
|
+
table_rows = get_table_rows(lines, table_pos)
|
837
|
+
|
838
|
+
num_headings = number_of_columns_in_a_table_row(lines[table_pos])
|
839
|
+
|
840
|
+
table_rows.each_with_index do |line, index|
|
841
|
+
if number_of_columns_in_a_table_row(line) != num_headings
|
842
|
+
error_lines << (table_pos + index + 1)
|
843
|
+
end
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
error_lines
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
rule 'MD057', 'Table has missing or invalid header separation (second row)' do
|
852
|
+
tags :tables
|
853
|
+
aliases 'table-invalid-second-row'
|
854
|
+
check do |doc|
|
855
|
+
error_lines = []
|
856
|
+
tables = doc.find_type_elements(:table)
|
857
|
+
lines = doc.lines
|
858
|
+
|
859
|
+
tables.each do |table|
|
860
|
+
second_row = ''
|
861
|
+
|
862
|
+
# line number of table start (1-indexed)
|
863
|
+
# which is equal to second row's index (0-indexed)
|
864
|
+
line_num = table.options[:location]
|
865
|
+
second_row = lines[line_num] if line_num < lines.length
|
866
|
+
|
867
|
+
# This pattern matches if
|
868
|
+
# 1) The row starts and stops with | characters
|
869
|
+
# 2) Only consists of characters '|', '-', ':' and whitespace
|
870
|
+
# 3) Each section between the separators (i.e. '|')
|
871
|
+
# a) has at least three consecutive dashes
|
872
|
+
# b) can have whitespace at the beginning or the end
|
873
|
+
# c) can have colon before and/or after dashes (for alignment)
|
874
|
+
# Some examples:
|
875
|
+
# |-----|----|-------| --> matches
|
876
|
+
# |:---:|:---|-------| --> matches
|
877
|
+
# | :------: | ----| --> matches
|
878
|
+
# | - - - | - - - | --> does NOT match
|
879
|
+
# |::---| --> does NOT match
|
880
|
+
# |----:|:--|----| --> does NOT match
|
881
|
+
pattern = /^(\|\s*:?-{3,}:?\s*)+\|$/
|
882
|
+
unless second_row.match(pattern)
|
883
|
+
# Second row is not in the form described by the pattern
|
884
|
+
error_lines << (line_num + 1)
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
error_lines
|
889
|
+
end
|
890
|
+
end
|
data/lib/mdl/ruleset.rb
CHANGED
@@ -48,6 +48,67 @@ module MarkdownLint
|
|
48
48
|
def docs_url
|
49
49
|
@generate_docs&.call(id, description)
|
50
50
|
end
|
51
|
+
|
52
|
+
# This method calculates the number of columns in a table row
|
53
|
+
#
|
54
|
+
# @param [String] table_row A row of the table in question.
|
55
|
+
# @return [Numeric] Number of columns in the row
|
56
|
+
def number_of_columns_in_a_table_row(table_row)
|
57
|
+
columns = table_row.strip.split('|')
|
58
|
+
|
59
|
+
if columns.empty?
|
60
|
+
# The stripped line consists of zero or more pipe characters
|
61
|
+
# and nothing more.
|
62
|
+
#
|
63
|
+
# Examples of stripped rows:
|
64
|
+
# '||' --> one column
|
65
|
+
# '|||' --> two columns
|
66
|
+
# '|' --> zero columns
|
67
|
+
[0, table_row.count('|') - 1].max
|
68
|
+
else
|
69
|
+
# Number of columns is the number of splited
|
70
|
+
# segments with pipe separator. The first segment
|
71
|
+
# is ignored when it's empty string because
|
72
|
+
# someting like '|1|2|' is split into ['', '1', '2']
|
73
|
+
# when using split('|') function.
|
74
|
+
#
|
75
|
+
# Some examples:
|
76
|
+
# '|foo|bar|' --> two columns
|
77
|
+
# ' |foo|bar|' --> two columns
|
78
|
+
# '|foo|bar' --> two columns
|
79
|
+
# 'foo|bar' --> two columns
|
80
|
+
columns.size - (columns[0].empty? ? 1 : 0)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# This method returns all the rows of a table
|
85
|
+
#
|
86
|
+
# @param [Array<String>] lines Lines of a doc as an array
|
87
|
+
# @param [Numeric] pos Position/index of the table in the array
|
88
|
+
# @return [Array<String>] Rows of the table in an array
|
89
|
+
def get_table_rows(lines, pos)
|
90
|
+
table_rows = []
|
91
|
+
while pos < lines.length
|
92
|
+
line = lines[pos]
|
93
|
+
|
94
|
+
# If the previous line is a table and the current line
|
95
|
+
# 1) includes pipe character
|
96
|
+
# 2) does not start with code block identifiers
|
97
|
+
# a) >= 4 spaces
|
98
|
+
# b) < 4 spaces and ``` right after
|
99
|
+
#
|
100
|
+
# it is possibly a table row
|
101
|
+
unless line.include?('|') && !line.start_with?(' ') &&
|
102
|
+
!line.strip.start_with?('```')
|
103
|
+
break
|
104
|
+
end
|
105
|
+
|
106
|
+
table_rows << line
|
107
|
+
pos += 1
|
108
|
+
end
|
109
|
+
|
110
|
+
table_rows
|
111
|
+
end
|
51
112
|
end
|
52
113
|
|
53
114
|
# defines a ruleset
|
data/lib/mdl/style.rb
CHANGED
@@ -58,7 +58,7 @@ module MarkdownLint
|
|
58
58
|
warn "#{style_file} does not appear to be a built-in style." +
|
59
59
|
' If you meant to pass in your own style file, it must contain' +
|
60
60
|
" a '/' or end in '.rb'. See https://github.com/markdownlint/" +
|
61
|
-
'markdownlint/blob/
|
61
|
+
'markdownlint/blob/main/docs/configuration.md'
|
62
62
|
exit(1)
|
63
63
|
end
|
64
64
|
style_file = tmp
|
data/lib/mdl/version.rb
CHANGED
data/lib/mdl.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require_relative 'mdl/formatters/sarif'
|
1
2
|
require_relative 'mdl/cli'
|
2
3
|
require_relative 'mdl/config'
|
3
4
|
require_relative 'mdl/doc'
|
@@ -103,7 +104,7 @@ module MarkdownLint
|
|
103
104
|
status = 1
|
104
105
|
error_lines.each do |line|
|
105
106
|
line += doc.offset # Correct line numbers for any yaml front matter
|
106
|
-
if Config[:json]
|
107
|
+
if Config[:json] || Config[:sarif]
|
107
108
|
results << {
|
108
109
|
'filename' => filename,
|
109
110
|
'line' => line,
|
@@ -118,12 +119,13 @@ module MarkdownLint
|
|
118
119
|
end
|
119
120
|
end
|
120
121
|
|
121
|
-
# If we're not in JSON mode (URLs are in the object), and we
|
122
|
-
# make real links (checking if we have a TTY is an OK heuristic
|
123
|
-
# that) then, instead of making the output ugly with long URLs, we
|
122
|
+
# If we're not in JSON or SARIF mode (URLs are in the object), and we
|
123
|
+
# cannot make real links (checking if we have a TTY is an OK heuristic
|
124
|
+
# for that) then, instead of making the output ugly with long URLs, we
|
124
125
|
# print them at the end. And of course we only want to print each URL
|
125
126
|
# once.
|
126
|
-
if !Config[:json] &&
|
127
|
+
if !Config[:json] && !Config[:sarif] &&
|
128
|
+
!$stdout.tty? && !docs_to_print.include?(rule)
|
127
129
|
docs_to_print << rule
|
128
130
|
end
|
129
131
|
end
|
@@ -132,6 +134,8 @@ module MarkdownLint
|
|
132
134
|
if Config[:json]
|
133
135
|
require 'json'
|
134
136
|
puts JSON.generate(results)
|
137
|
+
elsif Config[:sarif]
|
138
|
+
puts SarifFormatter.generate(rules, results)
|
135
139
|
elsif docs_to_print.any?
|
136
140
|
puts "\nFurther documentation is available for these failures:"
|
137
141
|
docs_to_print.each do |rule|
|
data/mdl.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.email = ['mark@mivok.net']
|
10
10
|
spec.summary = 'Markdown lint tool'
|
11
11
|
spec.description = 'Style checker/lint tool for markdown files'
|
12
|
-
spec.homepage = '
|
12
|
+
spec.homepage = 'https://github.com/markdownlint/markdownlint'
|
13
13
|
spec.license = 'MIT'
|
14
14
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
15
15
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mdl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Harrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kramdown
|
@@ -189,6 +189,7 @@ files:
|
|
189
189
|
- lib/mdl/cli.rb
|
190
190
|
- lib/mdl/config.rb
|
191
191
|
- lib/mdl/doc.rb
|
192
|
+
- lib/mdl/formatters/sarif.rb
|
192
193
|
- lib/mdl/kramdown_parser.rb
|
193
194
|
- lib/mdl/rules.rb
|
194
195
|
- lib/mdl/ruleset.rb
|
@@ -199,7 +200,7 @@ files:
|
|
199
200
|
- lib/mdl/styles/relaxed.rb
|
200
201
|
- lib/mdl/version.rb
|
201
202
|
- mdl.gemspec
|
202
|
-
homepage:
|
203
|
+
homepage: https://github.com/markdownlint/markdownlint
|
203
204
|
licenses:
|
204
205
|
- MIT
|
205
206
|
metadata:
|
@@ -219,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
219
220
|
- !ruby/object:Gem::Version
|
220
221
|
version: '0'
|
221
222
|
requirements: []
|
222
|
-
rubygems_version: 3.3.
|
223
|
+
rubygems_version: 3.3.15
|
223
224
|
signing_key:
|
224
225
|
specification_version: 4
|
225
226
|
summary: Markdown lint tool
|