mdl 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 240bbb771676be213b572426ae6aa48dd810cb11769263a30ee1e2b98b6751d5
4
- data.tar.gz: 0c474a99242d291a202b958294ba749977230b43b24a38342a00bc6675857d6b
3
+ metadata.gz: bb98b688ab9d9dc7bac3c169f20fefc7c0cb27ce5bf574bec52053d592b7a9c7
4
+ data.tar.gz: 873df51b4011d23617c1d73ab47a1928765fee0502b6f58853e2932bb3d5ec59
5
5
  SHA512:
6
- metadata.gz: 538a08a57be028038bc8e1fe72c241e81e57261b84a0b58efb6875b515c8b0dfb01715826959778c6d88a9c6bb2b799b668a1662ac09e1a9d1c8a8a67817addd
7
- data.tar.gz: 3104fb8e6bd66686b6268a5a5cb31f4bbdcef9fa7f7190f31b60a99c5acc489c47f19998c5bf40f9eff064c0f71cbeb73a8cb164a04899bb57666d041e85c125
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/master/docs/RULES.md##{url_hash}"
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 http://daringfireball.net/projects/markdown/syntax#header
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 indendation for each
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 => 0
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.extract_text(e, /^\s*> /)
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/master/docs/configuration.md'
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
@@ -1,3 +1,3 @@
1
1
  module MarkdownLint
2
- VERSION = '0.12.0'.freeze
2
+ VERSION = '0.13.0'.freeze
3
3
  end
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 cannot
122
- # make real links (checking if we have a TTY is an OK heuristic for
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] && !$stdout.tty? && !docs_to_print.include?(rule)
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 = 'http://github.com/markdownlint/markdownlint'
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.12.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: 2022-10-18 00:00:00.000000000 Z
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: http://github.com/markdownlint/markdownlint
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.7
223
+ rubygems_version: 3.3.15
223
224
  signing_key:
224
225
  specification_version: 4
225
226
  summary: Markdown lint tool