yamlint 0.1.0 → 1.0.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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +31 -9
  3. data/.serena/.gitignore +1 -0
  4. data/.serena/memories/project_overview.md +25 -0
  5. data/.serena/memories/style_and_conventions.md +7 -0
  6. data/.serena/memories/suggested_commands.md +17 -0
  7. data/.serena/memories/task_completion.md +5 -0
  8. data/.serena/project.yml +89 -0
  9. data/CHANGELOG.md +6 -2
  10. data/Gemfile +9 -7
  11. data/LICENSE.txt +1 -1
  12. data/README.md +155 -17
  13. data/Rakefile +10 -3
  14. data/docs/Yamlint/Cli.html +403 -0
  15. data/docs/Yamlint/Config.html +1034 -0
  16. data/docs/Yamlint/Formatter.html +433 -0
  17. data/docs/Yamlint/Linter.html +423 -0
  18. data/docs/Yamlint/Models/LintContext.html +993 -0
  19. data/docs/Yamlint/Models/Problem.html +951 -0
  20. data/docs/Yamlint/Models/Token.html +713 -0
  21. data/docs/Yamlint/Models.html +117 -0
  22. data/docs/Yamlint/Output/Base.html +290 -0
  23. data/docs/Yamlint/Output/Colored.html +293 -0
  24. data/docs/Yamlint/Output/Github.html +260 -0
  25. data/docs/Yamlint/Output/Parsable.html +258 -0
  26. data/docs/Yamlint/Output/Standard.html +270 -0
  27. data/docs/Yamlint/Output.html +208 -0
  28. data/docs/Yamlint/Parser/Comment.html +795 -0
  29. data/docs/Yamlint/Parser/CommentExtractor.html +287 -0
  30. data/docs/Yamlint/Parser/LineParser.html +423 -0
  31. data/docs/Yamlint/Parser/TokenParser/Handler.html +910 -0
  32. data/docs/Yamlint/Parser/TokenParser.html +291 -0
  33. data/docs/Yamlint/Parser.html +117 -0
  34. data/docs/Yamlint/Presets.html +266 -0
  35. data/docs/Yamlint/Rules/Anchors/AnchorHandler.html +678 -0
  36. data/docs/Yamlint/Rules/Anchors.html +314 -0
  37. data/docs/Yamlint/Rules/Base.html +968 -0
  38. data/docs/Yamlint/Rules/Braces.html +335 -0
  39. data/docs/Yamlint/Rules/Brackets.html +335 -0
  40. data/docs/Yamlint/Rules/Colons.html +313 -0
  41. data/docs/Yamlint/Rules/Commas.html +313 -0
  42. data/docs/Yamlint/Rules/CommentRule.html +298 -0
  43. data/docs/Yamlint/Rules/Comments.html +317 -0
  44. data/docs/Yamlint/Rules/CommentsIndentation.html +328 -0
  45. data/docs/Yamlint/Rules/DocumentEnd.html +332 -0
  46. data/docs/Yamlint/Rules/DocumentStart.html +332 -0
  47. data/docs/Yamlint/Rules/EmptyLines.html +300 -0
  48. data/docs/Yamlint/Rules/EmptyValues.html +273 -0
  49. data/docs/Yamlint/Rules/FloatValues.html +383 -0
  50. data/docs/Yamlint/Rules/Hyphens.html +337 -0
  51. data/docs/Yamlint/Rules/Indentation.html +329 -0
  52. data/docs/Yamlint/Rules/KeyDuplicates/DuplicateKeyHandler.html +716 -0
  53. data/docs/Yamlint/Rules/KeyDuplicates.html +258 -0
  54. data/docs/Yamlint/Rules/KeyOrdering/KeyOrderHandler.html +694 -0
  55. data/docs/Yamlint/Rules/KeyOrdering.html +258 -0
  56. data/docs/Yamlint/Rules/LineLength.html +251 -0
  57. data/docs/Yamlint/Rules/LineRule.html +304 -0
  58. data/docs/Yamlint/Rules/NewLineAtEndOfFile.html +308 -0
  59. data/docs/Yamlint/Rules/NewLines.html +310 -0
  60. data/docs/Yamlint/Rules/OctalValues.html +270 -0
  61. data/docs/Yamlint/Rules/QuotedStrings.html +381 -0
  62. data/docs/Yamlint/Rules/Registry.html +492 -0
  63. data/docs/Yamlint/Rules/TokenRule.html +298 -0
  64. data/docs/Yamlint/Rules/TrailingSpaces.html +321 -0
  65. data/docs/Yamlint/Rules/Truthy.html +288 -0
  66. data/docs/Yamlint/Rules.html +190 -0
  67. data/docs/Yamlint/Runner.html +551 -0
  68. data/docs/Yamlint.html +135 -0
  69. data/docs/_index.html +643 -0
  70. data/docs/class_list.html +54 -0
  71. data/docs/css/common.css +1 -0
  72. data/docs/css/full_list.css +58 -0
  73. data/docs/css/style.css +490 -0
  74. data/docs/file.README.html +276 -0
  75. data/docs/file_list.html +59 -0
  76. data/docs/frames.html +22 -0
  77. data/docs/index.html +276 -0
  78. data/docs/js/app.js +395 -0
  79. data/docs/js/full_list.js +244 -0
  80. data/docs/js/jquery.js +4 -0
  81. data/docs/method_list.html +1582 -0
  82. data/docs/top-level-namespace.html +110 -0
  83. data/exe/yamlint +7 -0
  84. data/lib/yamlint/cli.rb +162 -0
  85. data/lib/yamlint/config.rb +113 -0
  86. data/lib/yamlint/formatter.rb +140 -0
  87. data/lib/yamlint/linter.rb +130 -0
  88. data/lib/yamlint/models/lint_context.rb +43 -0
  89. data/lib/yamlint/models/problem.rb +44 -0
  90. data/lib/yamlint/models/token.rb +22 -0
  91. data/lib/yamlint/models.rb +9 -0
  92. data/lib/yamlint/output/base.rb +15 -0
  93. data/lib/yamlint/output/colored.rb +54 -0
  94. data/lib/yamlint/output/github.rb +20 -0
  95. data/lib/yamlint/output/parsable.rb +19 -0
  96. data/lib/yamlint/output/standard.rb +25 -0
  97. data/lib/yamlint/output.rb +26 -0
  98. data/lib/yamlint/parser/comment_extractor.rb +78 -0
  99. data/lib/yamlint/parser/line_parser.rb +30 -0
  100. data/lib/yamlint/parser/token_parser.rb +92 -0
  101. data/lib/yamlint/parser.rb +10 -0
  102. data/lib/yamlint/presets/default.rb +35 -0
  103. data/lib/yamlint/presets/relaxed.rb +34 -0
  104. data/lib/yamlint/presets.rb +19 -0
  105. data/lib/yamlint/rules/anchors.rb +133 -0
  106. data/lib/yamlint/rules/base.rb +102 -0
  107. data/lib/yamlint/rules/braces.rb +105 -0
  108. data/lib/yamlint/rules/brackets.rb +105 -0
  109. data/lib/yamlint/rules/colons.rb +73 -0
  110. data/lib/yamlint/rules/commas.rb +85 -0
  111. data/lib/yamlint/rules/comments.rb +77 -0
  112. data/lib/yamlint/rules/comments_indentation.rb +66 -0
  113. data/lib/yamlint/rules/document_end.rb +45 -0
  114. data/lib/yamlint/rules/document_start.rb +45 -0
  115. data/lib/yamlint/rules/empty_lines.rb +107 -0
  116. data/lib/yamlint/rules/empty_values.rb +43 -0
  117. data/lib/yamlint/rules/float_values.rb +67 -0
  118. data/lib/yamlint/rules/hyphens.rb +42 -0
  119. data/lib/yamlint/rules/indentation.rb +39 -0
  120. data/lib/yamlint/rules/key_duplicates.rb +127 -0
  121. data/lib/yamlint/rules/key_ordering.rb +126 -0
  122. data/lib/yamlint/rules/line_length.rb +57 -0
  123. data/lib/yamlint/rules/new_line_at_end_of_file.rb +31 -0
  124. data/lib/yamlint/rules/new_lines.rb +59 -0
  125. data/lib/yamlint/rules/octal_values.rb +62 -0
  126. data/lib/yamlint/rules/quoted_strings.rb +73 -0
  127. data/lib/yamlint/rules/registry.rb +48 -0
  128. data/lib/yamlint/rules/trailing_spaces.rb +31 -0
  129. data/lib/yamlint/rules/truthy.rb +48 -0
  130. data/lib/yamlint/rules.rb +41 -0
  131. data/lib/yamlint/runner.rb +80 -0
  132. data/lib/yamlint/version.rb +1 -1
  133. data/lib/yamlint.rb +11 -3
  134. metadata +131 -11
  135. data/CODE_OF_CONDUCT.md +0 -84
  136. data/sig/yamlint.rbs +0 -4
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Models
5
+ class Problem
6
+ attr_reader :line, :column, :rule, :level, :message, :fixable
7
+
8
+ LEVELS = %i[error warning info].freeze
9
+
10
+ def initialize(line:, column:, rule:, level:, message:, fixable: false)
11
+ @line = line
12
+ @column = column
13
+ @rule = rule
14
+ @level = validate_level(level)
15
+ @message = message
16
+ @fixable = fixable
17
+ end
18
+
19
+ def error?
20
+ @level == :error
21
+ end
22
+
23
+ def warning?
24
+ @level == :warning
25
+ end
26
+
27
+ def fixable?
28
+ @fixable
29
+ end
30
+
31
+ def to_s
32
+ "#{line}:#{column} [#{level}] #{message} (#{rule})"
33
+ end
34
+
35
+ private
36
+
37
+ def validate_level(level)
38
+ return level if LEVELS.include?(level)
39
+
40
+ raise ArgumentError, "Invalid level: #{level}. Must be one of #{LEVELS.join(', ')}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Models
5
+ class Token
6
+ attr_reader :type, :value, :start_line, :start_column, :end_line, :end_column
7
+
8
+ def initialize(type:, start_line:, start_column:, value: nil, end_line: nil, end_column: nil)
9
+ @type = type
10
+ @value = value
11
+ @start_line = start_line
12
+ @start_column = start_column
13
+ @end_line = end_line || start_line
14
+ @end_column = end_column || start_column
15
+ end
16
+
17
+ def to_s
18
+ "#{type}(#{start_line}:#{start_column})"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Models
5
+ autoload :Problem, 'yamlint/models/problem'
6
+ autoload :Token, 'yamlint/models/token'
7
+ autoload :LintContext, 'yamlint/models/lint_context'
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Output
5
+ class Base
6
+ def format(filepath, problems)
7
+ raise NotImplementedError, "#{self.class}#format must be implemented"
8
+ end
9
+
10
+ def format_summary(total_files, total_problems)
11
+ raise NotImplementedError, "#{self.class}#format_summary must be implemented"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Output
5
+ class Colored < Base
6
+ COLORS = {
7
+ reset: "\e[0m",
8
+ bold: "\e[1m",
9
+ red: "\e[31m",
10
+ yellow: "\e[33m",
11
+ cyan: "\e[36m",
12
+ dim: "\e[2m"
13
+ }.freeze
14
+
15
+ def format(filepath, problems)
16
+ return '' if problems.empty?
17
+
18
+ lines = ["#{COLORS[:bold]}#{filepath}#{COLORS[:reset]}"]
19
+ problems.each do |problem|
20
+ lines << format_problem(problem)
21
+ end
22
+ lines.join("\n")
23
+ end
24
+
25
+ def format_summary(total_files, total_problems)
26
+ if total_problems.zero?
27
+ format_success_summary(total_files)
28
+ else
29
+ format_failure_summary(total_files, total_problems)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def format_problem(problem)
36
+ color = problem.error? ? COLORS[:red] : COLORS[:yellow]
37
+ pos = "#{COLORS[:dim]}#{problem.line}:#{problem.column}#{COLORS[:reset]}"
38
+ level = "#{color}#{problem.level}#{COLORS[:reset]}"
39
+ rule = "#{COLORS[:cyan]}(#{problem.rule})#{COLORS[:reset]}"
40
+ " #{pos} #{level} #{problem.message} #{rule}"
41
+ end
42
+
43
+ def format_success_summary(total_files)
44
+ "#{COLORS[:bold]}#{total_files}#{COLORS[:reset]} file(s) checked, " \
45
+ "#{COLORS[:bold]}no problems found#{COLORS[:reset]}"
46
+ end
47
+
48
+ def format_failure_summary(total_files, total_problems)
49
+ "#{COLORS[:bold]}#{total_files}#{COLORS[:reset]} file(s) checked, " \
50
+ "#{COLORS[:red]}#{total_problems} problem(s) found#{COLORS[:reset]}"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Output
5
+ class Github < Base
6
+ def format(filepath, problems)
7
+ return '' if problems.empty?
8
+
9
+ problems.map do |problem|
10
+ type = problem.error? ? 'error' : 'warning'
11
+ "::#{type} file=#{filepath},line=#{problem.line},col=#{problem.column}::#{problem.message} (#{problem.rule})"
12
+ end.join("\n")
13
+ end
14
+
15
+ def format_summary(_total_files, _total_problems)
16
+ ''
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Output
5
+ class Parsable < Base
6
+ def format(filepath, problems)
7
+ return '' if problems.empty?
8
+
9
+ problems.map do |problem|
10
+ "#{filepath}:#{problem.line}:#{problem.column}: [#{problem.level}] #{problem.message} (#{problem.rule})"
11
+ end.join("\n")
12
+ end
13
+
14
+ def format_summary(_total_files, _total_problems)
15
+ ''
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Output
5
+ class Standard < Base
6
+ def format(filepath, problems)
7
+ return '' if problems.empty?
8
+
9
+ lines = [filepath]
10
+ problems.each do |problem|
11
+ lines << " #{problem.line}:#{problem.column} #{problem.level} #{problem.message} (#{problem.rule})"
12
+ end
13
+ lines.join("\n")
14
+ end
15
+
16
+ def format_summary(total_files, total_problems)
17
+ if total_problems.zero?
18
+ "#{total_files} file(s) checked, no problems found"
19
+ else
20
+ "#{total_files} file(s) checked, #{total_problems} problem(s) found"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Output
5
+ autoload :Base, 'yamlint/output/base'
6
+ autoload :Standard, 'yamlint/output/standard'
7
+ autoload :Parsable, 'yamlint/output/parsable'
8
+ autoload :Colored, 'yamlint/output/colored'
9
+ autoload :Github, 'yamlint/output/github'
10
+
11
+ def self.get(format)
12
+ case format.to_s
13
+ when 'standard'
14
+ Standard.new
15
+ when 'parsable'
16
+ Parsable.new
17
+ when 'colored', 'auto'
18
+ Colored.new
19
+ when 'github'
20
+ Github.new
21
+ else
22
+ raise ArgumentError, "Unknown output format: #{format}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Parser
5
+ class Comment
6
+ attr_reader :line_number, :column, :text, :inline
7
+
8
+ def initialize(line_number:, column:, text:, inline: false)
9
+ @line_number = line_number
10
+ @column = column
11
+ @text = text
12
+ @inline = inline
13
+ end
14
+
15
+ def inline?
16
+ @inline
17
+ end
18
+
19
+ def disable_directive?
20
+ @text.match?(/yamllint\s+disable(-line)?/)
21
+ end
22
+
23
+ def enable_directive?
24
+ @text.match?(/yamllint\s+enable/)
25
+ end
26
+
27
+ def disabled_rules
28
+ if (match = @text.match(/yamllint\s+disable(-line)?\s+(.+)/))
29
+ match[2].split(/[,\s]+/).map(&:strip).reject(&:empty?)
30
+ else
31
+ []
32
+ end
33
+ end
34
+ end
35
+
36
+ class CommentExtractor
37
+ COMMENT_PATTERN = /#(.*)$/
38
+
39
+ def initialize(content)
40
+ @content = content
41
+ end
42
+
43
+ def extract
44
+ comments = []
45
+ @content.lines.each_with_index do |line, index|
46
+ line_number = index + 1
47
+ extract_comments_from_line(line, line_number, comments)
48
+ end
49
+ comments
50
+ end
51
+
52
+ private
53
+
54
+ def extract_comments_from_line(line, line_number, comments)
55
+ return if in_string?(line)
56
+
57
+ if (match = line.match(COMMENT_PATTERN))
58
+ column = match.begin(0) + 1
59
+ text = match[1].strip
60
+ inline = column > 1 && !line[0...(column - 1)].strip.empty?
61
+
62
+ comments << Comment.new(
63
+ line_number:,
64
+ column:,
65
+ text:,
66
+ inline:
67
+ )
68
+ end
69
+ end
70
+
71
+ def in_string?(_line)
72
+ # Simple heuristic: skip lines that start with quotes
73
+ # A more complete implementation would track string state
74
+ false
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Parser
5
+ class LineParser
6
+ def initialize(content)
7
+ @content = content
8
+ end
9
+
10
+ def parse
11
+ @content.lines
12
+ end
13
+
14
+ def line_at(line_number)
15
+ lines = parse
16
+ lines[line_number - 1]
17
+ end
18
+
19
+ def line_count
20
+ parse.length
21
+ end
22
+
23
+ def each_line_with_number(&)
24
+ parse.each_with_index do |line, index|
25
+ yield(line, index + 1)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Yamlint
6
+ module Parser
7
+ class TokenParser
8
+ class Handler < Psych::Handler
9
+ attr_reader :tokens
10
+
11
+ def initialize
12
+ super
13
+ @tokens = []
14
+ @parser = nil
15
+ end
16
+
17
+ attr_writer :parser
18
+
19
+ def start_stream(encoding)
20
+ add_token(:stream_start, encoding:)
21
+ end
22
+
23
+ def end_stream
24
+ add_token(:stream_end)
25
+ end
26
+
27
+ def start_document(version, tag_directives, implicit)
28
+ add_token(:document_start, version:, tag_directives:, implicit:)
29
+ end
30
+
31
+ def end_document(implicit)
32
+ add_token(:document_end, implicit:)
33
+ end
34
+
35
+ def start_mapping(anchor, tag, implicit, style)
36
+ add_token(:mapping_start, anchor:, tag:, implicit:, style:)
37
+ end
38
+
39
+ def end_mapping
40
+ add_token(:mapping_end)
41
+ end
42
+
43
+ def start_sequence(anchor, tag, implicit, style)
44
+ add_token(:sequence_start, anchor:, tag:, implicit:, style:)
45
+ end
46
+
47
+ def end_sequence
48
+ add_token(:sequence_end)
49
+ end
50
+
51
+ def scalar(value, anchor, tag, plain, quoted, style)
52
+ add_token(:scalar, value:, anchor:, tag:, plain:, quoted:, style:)
53
+ end
54
+
55
+ def alias(anchor)
56
+ add_token(:alias, anchor:)
57
+ end
58
+
59
+ private
60
+
61
+ def add_token(type, **attrs)
62
+ mark = @parser&.mark
63
+ token = Models::Token.new(
64
+ type:,
65
+ value: attrs[:value],
66
+ start_line: mark ? mark.line + 1 : 1,
67
+ start_column: mark ? mark.column + 1 : 1
68
+ )
69
+ @tokens << token
70
+ end
71
+ end
72
+
73
+ def initialize(content)
74
+ @content = content
75
+ end
76
+
77
+ def parse
78
+ handler = Handler.new
79
+ parser = Psych::Parser.new(handler)
80
+ handler.parser = parser
81
+
82
+ begin
83
+ parser.parse(@content)
84
+ rescue Psych::SyntaxError
85
+ # Syntax errors will be handled separately
86
+ end
87
+
88
+ handler.tokens
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Parser
5
+ autoload :LineParser, 'yamlint/parser/line_parser'
6
+ autoload :TokenParser, 'yamlint/parser/token_parser'
7
+ autoload :CommentExtractor, 'yamlint/parser/comment_extractor'
8
+ autoload :Comment, 'yamlint/parser/comment_extractor'
9
+ end
10
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Presets
5
+ DEFAULT = {
6
+ rules: {
7
+ 'anchors' => { 'forbid-undeclared-aliases' => true, 'forbid-duplicated-anchors' => true,
8
+ 'forbid-unused-anchors' => true },
9
+ 'braces' => { 'forbid' => false, 'min-spaces-inside' => 0, 'max-spaces-inside' => 0 },
10
+ 'brackets' => { 'forbid' => false, 'min-spaces-inside' => 0, 'max-spaces-inside' => 0 },
11
+ 'colons' => { 'max-spaces-before' => 0, 'max-spaces-after' => 1 },
12
+ 'commas' => { 'max-spaces-before' => 0, 'min-spaces-after' => 1, 'max-spaces-after' => 1 },
13
+ 'comments' => { 'require-starting-space' => true, 'ignore-shebangs' => true, 'min-spaces-from-content' => 2 },
14
+ 'comments-indentation' => {},
15
+ 'document-end' => 'disable',
16
+ 'document-start' => 'disable',
17
+ 'empty-lines' => { 'max' => 2, 'max-start' => 0, 'max-end' => 0 },
18
+ 'empty-values' => { 'forbid-in-block-mappings' => true, 'forbid-in-flow-mappings' => true },
19
+ 'float-values' => 'disable',
20
+ 'hyphens' => { 'max-spaces-after' => 1 },
21
+ 'indentation' => { 'spaces' => 2, 'indent-sequences' => true },
22
+ 'key-duplicates' => {},
23
+ 'key-ordering' => 'disable',
24
+ 'line-length' => { 'max' => 80, 'allow-non-breakable-words' => true,
25
+ 'allow-non-breakable-inline-mappings' => true },
26
+ 'new-line-at-end-of-file' => {},
27
+ 'new-lines' => { 'type' => 'unix' },
28
+ 'octal-values' => { 'forbid-implicit-octal' => true, 'forbid-explicit-octal' => false },
29
+ 'quoted-strings' => 'disable',
30
+ 'trailing-spaces' => {},
31
+ 'truthy' => { 'allowed-values' => %w[true false], 'check-keys' => true }
32
+ }
33
+ }.freeze
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yamlint
4
+ module Presets
5
+ RELAXED = {
6
+ rules: {
7
+ 'anchors' => 'disable',
8
+ 'braces' => { 'forbid' => false, 'min-spaces-inside' => 0, 'max-spaces-inside' => 1 },
9
+ 'brackets' => { 'forbid' => false, 'min-spaces-inside' => 0, 'max-spaces-inside' => 1 },
10
+ 'colons' => { 'max-spaces-before' => 1, 'max-spaces-after' => 1 },
11
+ 'commas' => { 'max-spaces-before' => 1, 'min-spaces-after' => 1, 'max-spaces-after' => 1 },
12
+ 'comments' => 'disable',
13
+ 'comments-indentation' => 'disable',
14
+ 'document-end' => 'disable',
15
+ 'document-start' => 'disable',
16
+ 'empty-lines' => 'disable',
17
+ 'empty-values' => 'disable',
18
+ 'float-values' => 'disable',
19
+ 'hyphens' => 'disable',
20
+ 'indentation' => 'disable',
21
+ 'key-duplicates' => {},
22
+ 'key-ordering' => 'disable',
23
+ 'line-length' => { 'max' => 120, 'allow-non-breakable-words' => true,
24
+ 'allow-non-breakable-inline-mappings' => true },
25
+ 'new-line-at-end-of-file' => 'disable',
26
+ 'new-lines' => { 'type' => 'unix' },
27
+ 'octal-values' => 'disable',
28
+ 'quoted-strings' => 'disable',
29
+ 'trailing-spaces' => 'disable',
30
+ 'truthy' => 'disable'
31
+ }
32
+ }.freeze
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'presets/default'
4
+ require_relative 'presets/relaxed'
5
+
6
+ module Yamlint
7
+ module Presets
8
+ def self.get(name)
9
+ case name.to_s
10
+ when 'default'
11
+ DEFAULT
12
+ when 'relaxed'
13
+ RELAXED
14
+ else
15
+ raise ArgumentError, "Unknown preset: #{name}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Yamlint
6
+ module Rules
7
+ class Anchors < Base
8
+ rule_id 'anchors'
9
+ desc 'Check anchors are defined before use and are not duplicated.'
10
+ defaults({
11
+ 'forbid-undeclared-aliases': true,
12
+ 'forbid-duplicated-anchors': true,
13
+ 'forbid-unused-anchors': true
14
+ })
15
+
16
+ def check(context)
17
+ handler = AnchorHandler.new
18
+ parser = Psych::Parser.new(handler)
19
+ handler.parser = parser
20
+
21
+ begin
22
+ parser.parse(context.content)
23
+ rescue Psych::SyntaxError
24
+ return []
25
+ end
26
+
27
+ problems = []
28
+
29
+ if @config[:'forbid-undeclared-aliases']
30
+ handler.undeclared_aliases.each do |alias_info|
31
+ problems << problem(
32
+ line: alias_info[:line],
33
+ column: alias_info[:column],
34
+ message: "found undefined alias \"#{alias_info[:name]}\"",
35
+ fixable: false
36
+ )
37
+ end
38
+ end
39
+
40
+ if @config[:'forbid-duplicated-anchors']
41
+ handler.duplicated_anchors.each do |anchor_info|
42
+ problems << problem(
43
+ line: anchor_info[:line],
44
+ column: anchor_info[:column],
45
+ message: "found duplicate anchor \"#{anchor_info[:name]}\"",
46
+ fixable: false
47
+ )
48
+ end
49
+ end
50
+
51
+ if @config[:'forbid-unused-anchors']
52
+ handler.unused_anchors.each do |anchor_info|
53
+ problems << problem(
54
+ line: anchor_info[:line],
55
+ column: anchor_info[:column],
56
+ message: "found undefined anchor \"#{anchor_info[:name]}\"",
57
+ fixable: false
58
+ )
59
+ end
60
+ end
61
+
62
+ problems
63
+ end
64
+
65
+ class AnchorHandler < Psych::Handler
66
+ attr_accessor :parser
67
+
68
+ def initialize
69
+ super
70
+ @anchors = {}
71
+ @aliases = []
72
+ @parser = nil
73
+ end
74
+
75
+ def scalar(_value, anchor, _tag, _plain, _quoted, _style)
76
+ record_anchor(anchor) if anchor
77
+ end
78
+
79
+ def start_mapping(anchor, _tag, _implicit, _style)
80
+ record_anchor(anchor) if anchor
81
+ end
82
+
83
+ def start_sequence(anchor, _tag, _implicit, _style)
84
+ record_anchor(anchor) if anchor
85
+ end
86
+
87
+ def alias(anchor)
88
+ mark = @parser&.mark
89
+ @aliases << {
90
+ name: anchor,
91
+ line: mark ? mark.line + 1 : 1,
92
+ column: mark ? mark.column + 1 : 1
93
+ }
94
+ end
95
+
96
+ def undeclared_aliases
97
+ @aliases.reject { |a| @anchors.key?(a[:name]) }
98
+ end
99
+
100
+ def duplicated_anchors
101
+ @anchors.values.select { |v| v.is_a?(Array) && v.length > 1 }.flatten
102
+ end
103
+
104
+ def unused_anchors
105
+ used_names = @aliases.map { |a| a[:name] }
106
+ @anchors.except(*used_names).map do |_, info|
107
+ info.is_a?(Array) ? info.first : info
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def record_anchor(anchor)
114
+ mark = @parser&.mark
115
+ info = {
116
+ name: anchor,
117
+ line: mark ? mark.line + 1 : 1,
118
+ column: mark ? mark.column + 1 : 1
119
+ }
120
+
121
+ if @anchors.key?(anchor)
122
+ existing = @anchors[anchor]
123
+ @anchors[anchor] = existing.is_a?(Array) ? existing + [info] : [existing, info]
124
+ else
125
+ @anchors[anchor] = info
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ Registry.register(Anchors)
132
+ end
133
+ end