haml_lint 0.21.0 → 0.22.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +18 -0
  3. data/lib/haml_lint/adapter/haml_4.rb +40 -0
  4. data/lib/haml_lint/adapter/haml_5.rb +46 -0
  5. data/lib/haml_lint/adapter.rb +36 -0
  6. data/lib/haml_lint/cli.rb +12 -9
  7. data/lib/haml_lint/comment_configuration.rb +39 -0
  8. data/lib/haml_lint/directive.rb +128 -0
  9. data/lib/haml_lint/document.rb +3 -1
  10. data/lib/haml_lint/exceptions.rb +6 -0
  11. data/lib/haml_lint/haml_visitor.rb +4 -2
  12. data/lib/haml_lint/lint.rb +2 -2
  13. data/lib/haml_lint/linter/alignment_tabs.rb +12 -0
  14. data/lib/haml_lint/linter/consecutive_comments.rb +19 -2
  15. data/lib/haml_lint/linter/consecutive_silent_scripts.rb +18 -2
  16. data/lib/haml_lint/linter/final_newline.rb +6 -5
  17. data/lib/haml_lint/linter/id_names.rb +28 -0
  18. data/lib/haml_lint/linter/indentation.rb +4 -2
  19. data/lib/haml_lint/linter/instance_variables.rb +77 -0
  20. data/lib/haml_lint/linter/line_length.rb +5 -3
  21. data/lib/haml_lint/linter/repeated_id.rb +34 -0
  22. data/lib/haml_lint/linter/syntax.rb +6 -0
  23. data/lib/haml_lint/linter/trailing_whitespace.rb +5 -4
  24. data/lib/haml_lint/linter.rb +1 -1
  25. data/lib/haml_lint/logger.rb +7 -1
  26. data/lib/haml_lint/options.rb +20 -4
  27. data/lib/haml_lint/parsed_ruby.rb +22 -0
  28. data/lib/haml_lint/rake_task.rb +16 -2
  29. data/lib/haml_lint/report.rb +46 -2
  30. data/lib/haml_lint/reporter/default_reporter.rb +7 -29
  31. data/lib/haml_lint/reporter/hash_reporter.rb +51 -0
  32. data/lib/haml_lint/reporter/hooks.rb +25 -0
  33. data/lib/haml_lint/reporter/json_reporter.rb +2 -45
  34. data/lib/haml_lint/reporter/progress_reporter.rb +47 -0
  35. data/lib/haml_lint/reporter/utils.rb +101 -0
  36. data/lib/haml_lint/reporter.rb +4 -0
  37. data/lib/haml_lint/runner.rb +70 -10
  38. data/lib/haml_lint/severity.rb +95 -0
  39. data/lib/haml_lint/tree/haml_comment_node.rb +18 -0
  40. data/lib/haml_lint/tree/node.rb +116 -16
  41. data/lib/haml_lint/tree/null_node.rb +15 -0
  42. data/lib/haml_lint/tree/root_node.rb +16 -0
  43. data/lib/haml_lint/tree/script_node.rb +9 -0
  44. data/lib/haml_lint/tree/silent_script_node.rb +7 -0
  45. data/lib/haml_lint/tree/tag_node.rb +24 -5
  46. data/lib/haml_lint/version.rb +1 -1
  47. data/lib/haml_lint.rb +1 -0
  48. metadata +41 -4
@@ -7,14 +7,16 @@ module HamlLint
7
7
 
8
8
  MSG = 'Line is too long. [%d/%d]'.freeze
9
9
 
10
- def visit_root(_node)
10
+ def visit_root(root)
11
11
  max_length = config['max']
12
- dummy_node = Struct.new(:line)
13
12
 
14
13
  document.source_lines.each_with_index do |line, index|
15
14
  next if line.length <= max_length
16
15
 
17
- record_lint(dummy_node.new(index + 1), format(MSG, line.length, max_length))
16
+ node = root.node_for_line(index + 1)
17
+ unless node.disabled?(self)
18
+ record_lint(node, format(MSG, line.length, max_length))
19
+ end
18
20
  end
19
21
  end
20
22
  end
@@ -0,0 +1,34 @@
1
+ module HamlLint
2
+ # Detects repeated instances of an element ID in a file
3
+ class Linter::RepeatedId < Linter
4
+ include LinterRegistry
5
+
6
+ MESSAGE_FORMAT = %{Do not repeat id "#%s" on the page}.freeze
7
+
8
+ def visit_tag(node)
9
+ id = node.tag_id
10
+ return unless id && !id.empty?
11
+
12
+ nodes = (id_map[id] << node)
13
+ case nodes.size
14
+ when 1 then return
15
+ when 2 then add_lints_for_first_duplications(nodes)
16
+ else add_lint(node, id)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def add_lint(node, id)
23
+ record_lint(node, MESSAGE_FORMAT % id)
24
+ end
25
+
26
+ def add_lints_for_first_duplications(nodes)
27
+ nodes.each { |node| add_lint(node, node.tag_id) }
28
+ end
29
+
30
+ def id_map
31
+ @id_map ||= Hash.new { |hash, key| hash[key] = [] }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,6 @@
1
+ module HamlLint
2
+ # A catch-all linter for syntax violations raised by the Haml parser.
3
+ class Linter::Syntax < Linter
4
+ include LinterRegistry
5
+ end
6
+ end
@@ -3,13 +3,14 @@ module HamlLint
3
3
  class Linter::TrailingWhitespace < Linter
4
4
  include LinterRegistry
5
5
 
6
- def visit_root(_node)
7
- dummy_node = Struct.new(:line)
8
-
6
+ def visit_root(root)
9
7
  document.source_lines.each_with_index do |line, index|
10
8
  next unless line =~ /\s+$/
11
9
 
12
- record_lint dummy_node.new(index + 1), 'Line contains trailing whitespace'
10
+ node = root.node_for_line(index + 1)
11
+ unless node.disabled?(self)
12
+ record_lint node, 'Line contains trailing whitespace'
13
+ end
13
14
  end
14
15
  end
15
16
  end
@@ -33,7 +33,7 @@ module HamlLint
33
33
  #
34
34
  # @return [String]
35
35
  def name
36
- self.class.name.split('::').last
36
+ self.class.name.to_s.split('::').last
37
37
  end
38
38
 
39
39
  private
@@ -5,6 +5,10 @@ module HamlLint
5
5
  # @return [true,false]
6
6
  attr_accessor :color_enabled
7
7
 
8
+ # Whether to output a summary in the log for certain reporters.
9
+ # @return [true,false]
10
+ attr_accessor :summary_enabled
11
+
8
12
  # Creates a logger which outputs nothing.
9
13
  # @return [HamlLint::Logger]
10
14
  def self.silent
@@ -14,8 +18,10 @@ module HamlLint
14
18
  # Creates a new {HamlLint::Logger} instance.
15
19
  #
16
20
  # @param out [IO] the output destination.
17
- def initialize(out)
21
+ # @param summary [true,false] whether to print summaries
22
+ def initialize(out, summary: true)
18
23
  @out = out
24
+ @summary_enabled = summary
19
25
  end
20
26
 
21
27
  # Print the specified output.
@@ -15,6 +15,7 @@ module HamlLint
15
15
 
16
16
  add_linter_options parser
17
17
  add_file_options parser
18
+ add_logger_options parser
18
19
  add_info_options parser
19
20
  end.parse!(args)
20
21
 
@@ -45,6 +46,15 @@ module HamlLint
45
46
  'Specify which reporter you want to use to generate the output') do |reporter|
46
47
  @options[:reporter] = load_reporter_class(reporter.capitalize)
47
48
  end
49
+
50
+ parser.on('--fail-fast', 'Fail after the first file with lint above the fail level') do
51
+ @options[:fail_fast] = true
52
+ end
53
+
54
+ parser.on('--fail-level fail_level', String,
55
+ 'Specify which level you want the suite to fail') do |fail_level|
56
+ @options[:fail_level] = HamlLint::Severity.new(fail_level.to_sym)
57
+ end
48
58
  end
49
59
 
50
60
  # Returns the class of the specified Reporter.
@@ -80,10 +90,6 @@ module HamlLint
80
90
  @options[:show_reporters] = true
81
91
  end
82
92
 
83
- parser.on('--[no-]color', 'Force output to be colorized') do |color|
84
- @options[:color] = color
85
- end
86
-
87
93
  parser.on_tail('-h', '--help', 'Display help documentation') do
88
94
  @options[:help] = parser.help
89
95
  end
@@ -96,5 +102,15 @@ module HamlLint
96
102
  @options[:verbose_version] = true
97
103
  end
98
104
  end
105
+
106
+ def add_logger_options(parser)
107
+ parser.on('--[no-]color', 'Force output to be colorized') do |color|
108
+ @options[:color] = color
109
+ end
110
+
111
+ parser.on('--[no-]summary', 'Print a summary of your linting report') do |summary|
112
+ @options[:summary] = summary
113
+ end
114
+ end
99
115
  end
100
116
  end
@@ -0,0 +1,22 @@
1
+ require 'delegate'
2
+
3
+ module HamlLint
4
+ # A thin wrapper around the syntax tree from the Parser gem.
5
+ class ParsedRuby < SimpleDelegator
6
+ # !@method syntax_tree
7
+ # Returns the bare syntax tree from the wrapper.
8
+ #
9
+ # @api semipublic
10
+ # @return [Array] syntax tree in the form returned by Parser gem
11
+ alias syntax_tree __getobj__
12
+
13
+ # Checks whether the syntax tree contains any instance variables.
14
+ #
15
+ # @return [true, false]
16
+ def contains_instance_variables?
17
+ return false unless syntax_tree
18
+
19
+ syntax_tree.ivar_type? || syntax_tree.each_descendant.any?(&:ivar_type?)
20
+ end
21
+ end
22
+ end
@@ -51,6 +51,16 @@ module HamlLint
51
51
  # @return [true,false]
52
52
  attr_accessor :quiet
53
53
 
54
+ # The severity level above which we should fail the Rake task.
55
+ #
56
+ # @example
57
+ # HamlLint::RakeTask.new do |task|
58
+ # task.fail_level = 'error'
59
+ # end
60
+ #
61
+ # @return [String]
62
+ attr_accessor :fail_level
63
+
54
64
  # Create the task so it exists in the current namespace.
55
65
  #
56
66
  # @param name [Symbol] task name
@@ -82,8 +92,7 @@ module HamlLint
82
92
  #
83
93
  # @param task_args [Rake::TaskArguments]
84
94
  def run_cli(task_args)
85
- cli_args = ['--config', config] if config
86
-
95
+ cli_args = parse_args
87
96
  logger = quiet ? HamlLint::Logger.silent : HamlLint::Logger.new(STDOUT)
88
97
  result = HamlLint::CLI.new(logger).run(Array(cli_args) + files_to_lint(task_args))
89
98
 
@@ -118,5 +127,10 @@ module HamlLint
118
127
  description += ' [files...]`'
119
128
  description
120
129
  end
130
+
131
+ def parse_args
132
+ cli_args = config ? ['--config', config] : []
133
+ cli_args.concat(['--fail-level', fail_level]) if fail_level
134
+ end
121
135
  end
122
136
  end
@@ -4,6 +4,9 @@ module HamlLint
4
4
  # List of lints that were found.
5
5
  attr_accessor :lints
6
6
 
7
+ # The level of lint to fail after detecting
8
+ attr_reader :fail_level
9
+
7
10
  # List of files that were linted.
8
11
  attr_reader :files
9
12
 
@@ -11,13 +14,54 @@ module HamlLint
11
14
  #
12
15
  # @param lints [Array<HamlLint::Lint>] lints that were found
13
16
  # @param files [Array<String>] files that were linted
14
- def initialize(lints, files)
17
+ # @param fail_level [Symbol] the severity level to fail on
18
+ # @param reporter [HamlLint::Reporter] the reporter for the report
19
+ def initialize(lints = [], files = [], fail_level = :warning, reporter:)
15
20
  @lints = lints.sort_by { |l| [l.filename, l.line] }
16
21
  @files = files
22
+ @fail_level = Severity.new(fail_level)
23
+ @reporter = reporter
24
+ end
25
+
26
+ # Adds a lint to the report and notifies the reporter.
27
+ #
28
+ # @param lint [HamlLint::Lint] lint to add
29
+ # @return [void]
30
+ def add_lint(lint)
31
+ lints << lint
32
+ @reporter.added_lint(lint)
33
+ end
34
+
35
+ # Displays the report via the configured reporter.
36
+ #
37
+ # @return [void]
38
+ def display
39
+ @reporter.display_report(self)
17
40
  end
18
41
 
42
+ # Checks whether any lints were over the fail level
43
+ #
44
+ # @return [Boolean]
19
45
  def failed?
20
- @lints.any?
46
+ @lints.any? { |lint| lint.severity >= fail_level }
47
+ end
48
+
49
+ # Adds a file to the list of linted files and notifies the reporter.
50
+ #
51
+ # @param file [String] the name of the file that was finished
52
+ # @param lints [Array<HamlLint::Lint>] the lints for the finished file
53
+ # @return [void]
54
+ def finish_file(file, lints)
55
+ files << file
56
+ @reporter.finished_file(file, lints)
57
+ end
58
+
59
+ # Notifies the reporter that the report has started.
60
+ #
61
+ # @param files [Array<String>] the files to lint
62
+ # @return [void]
63
+ def start(files)
64
+ @reporter.start(files)
21
65
  end
22
66
  end
23
67
  end
@@ -1,39 +1,17 @@
1
+ require 'haml_lint/reporter/utils'
2
+
1
3
  module HamlLint
2
4
  # Outputs lints in a simple format with the filename, line number, and lint
3
5
  # message.
4
6
  class Reporter::DefaultReporter < Reporter
5
- def display_report(report)
6
- sorted_lints = report.lints.sort_by { |l| [l.filename, l.line] }
7
-
8
- sorted_lints.each do |lint|
9
- print_location(lint)
10
- print_type(lint)
11
- print_message(lint)
12
- end
13
- end
14
-
15
- private
7
+ include Reporter::Utils
16
8
 
17
- def print_location(lint)
18
- log.info lint.filename, false
19
- log.log ':', false
20
- log.bold lint.line, false
9
+ def added_lint(lint)
10
+ print_lint(lint)
21
11
  end
22
12
 
23
- def print_type(lint)
24
- if lint.error?
25
- log.error ' [E] ', false
26
- else
27
- log.warning ' [W] ', false
28
- end
29
- end
30
-
31
- def print_message(lint)
32
- if lint.linter
33
- log.success("#{lint.linter.name}: ", false)
34
- end
35
-
36
- log.log lint.message
13
+ def display_report(report)
14
+ print_summary(report)
37
15
  end
38
16
  end
39
17
  end
@@ -0,0 +1,51 @@
1
+ module HamlLint
2
+ # Outputs report as a Ruby Hash for easy use by other tools.
3
+ class Reporter::HashReporter < Reporter
4
+ def display_report(report)
5
+ lints = report.lints
6
+ grouped = lints.group_by(&:filename)
7
+
8
+ report_hash = {
9
+ metadata: metadata,
10
+ files: grouped.map { |l| map_file(l) },
11
+ summary: {
12
+ offense_count: lints.length,
13
+ target_file_count: grouped.length,
14
+ inspected_file_count: report.files.length,
15
+ },
16
+ }
17
+
18
+ report_hash
19
+ end
20
+
21
+ private
22
+
23
+ def metadata
24
+ {
25
+ haml_lint_version: HamlLint::VERSION,
26
+ ruby_engine: RUBY_ENGINE,
27
+ ruby_patchlevel: RUBY_PATCHLEVEL.to_s,
28
+ ruby_platform: RUBY_PLATFORM,
29
+ }
30
+ end
31
+
32
+ def map_file(file)
33
+ {
34
+ path: file.first,
35
+ offenses: file.last.map { |o| map_offense(o) },
36
+ }
37
+ end
38
+
39
+ def map_offense(offense)
40
+ {
41
+ severity: offense.severity,
42
+ message: offense.message,
43
+ location: {
44
+ line: offense.line,
45
+ },
46
+ }.tap do |h|
47
+ h[:linter_name] = offense.linter.name if offense.linter
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ module HamlLint
2
+ class Reporter
3
+ # A collection of hook methods for incremental processing.
4
+ module Hooks
5
+ # A hook that is called for each lint as it is detected.
6
+ #
7
+ # @param _lint [HamlLint::Lint] the lint added to the report
8
+ # @return [void]
9
+ def added_lint(_lint); end
10
+
11
+ # A hook that is called for each file as it is finished processing.
12
+ #
13
+ # @param _file [String] the name of the file that just finished
14
+ # @param _lints [Array<HamlLint::Lint>] the lints added to the report
15
+ # @return [void]
16
+ def finished_file(_file, _lints); end
17
+
18
+ # A hook that is called when the processing starts.
19
+ #
20
+ # @param _files [Array<String>] the names of the files to be processed
21
+ # @return [void]
22
+ def start(_files); end
23
+ end
24
+ end
25
+ end
@@ -1,51 +1,8 @@
1
1
  module HamlLint
2
2
  # Outputs report as a JSON document.
3
- class Reporter::JsonReporter < Reporter
3
+ class Reporter::JsonReporter < Reporter::HashReporter
4
4
  def display_report(report)
5
- lints = report.lints
6
- grouped = lints.group_by(&:filename)
7
-
8
- report_hash = {
9
- metadata: metadata,
10
- files: grouped.map { |l| map_file(l) },
11
- summary: {
12
- offense_count: lints.length,
13
- target_file_count: grouped.length,
14
- inspected_file_count: report.files.length,
15
- },
16
- }
17
-
18
- log.log report_hash.to_json
19
- end
20
-
21
- private
22
-
23
- def metadata
24
- {
25
- haml_lint_version: HamlLint::VERSION,
26
- ruby_engine: RUBY_ENGINE,
27
- ruby_patchlevel: RUBY_PATCHLEVEL.to_s,
28
- ruby_platform: RUBY_PLATFORM,
29
- }
30
- end
31
-
32
- def map_file(file)
33
- {
34
- path: file.first,
35
- offenses: file.last.map { |o| map_offense(o) },
36
- }
37
- end
38
-
39
- def map_offense(offense)
40
- {
41
- severity: offense.severity,
42
- message: offense.message,
43
- location: {
44
- line: offense.line,
45
- },
46
- }.tap do |h|
47
- h[:linter_name] = offense.linter.name if offense.linter
48
- end
5
+ log.log super.to_json
49
6
  end
50
7
  end
51
8
  end
@@ -0,0 +1,47 @@
1
+ require 'rainbow'
2
+ require 'haml_lint/reporter/utils'
3
+
4
+ module HamlLint
5
+ # Outputs files as they are output as a simple symbol, then outputs
6
+ # a summary of each lint.
7
+ class Reporter::ProgressReporter < Reporter
8
+ include Reporter::Utils
9
+
10
+ DOT = '.'.freeze
11
+
12
+ def display_report(report)
13
+ lints = report.lints
14
+
15
+ log.log("\n\nOffenses:\n", true) if lints.any?
16
+ lints.each { |lint| print_lint(lint) }
17
+
18
+ print_summary(report)
19
+ end
20
+
21
+ def finished_file(_file, lints)
22
+ report_file_as_mark(lints)
23
+ end
24
+
25
+ def start(files)
26
+ log.log("Inspecting #{pluralize('file', count: files.size)}", true)
27
+ end
28
+
29
+ private
30
+
31
+ def dot
32
+ @dot ||= Rainbow(DOT).green
33
+ end
34
+
35
+ def report_file_as_mark(lints)
36
+ mark =
37
+ if lints.empty?
38
+ dot
39
+ else
40
+ worst_lint = lints.max_by(&:severity)
41
+ worst_lint.severity.mark_with_color
42
+ end
43
+
44
+ log.log(mark, false)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,101 @@
1
+ module HamlLint
2
+ class Reporter
3
+ # Formatting helpers for printing the default report format.
4
+ module Utils
5
+ # Pluralizes a word based on a count.
6
+ #
7
+ # @param word [String] the word to pluralize
8
+ # @param count [Integer] the count of items
9
+ # @return [String]
10
+ def pluralize(word, count: 1)
11
+ if count.zero? || count > 1
12
+ "#{count} #{word}s"
13
+ else
14
+ "#{count} #{word}"
15
+ end
16
+ end
17
+
18
+ # Prints the lint with its location and severity.
19
+ #
20
+ # @param lint [HamlLint::Lint] the lint to print
21
+ # @return [void]
22
+ def print_lint(lint)
23
+ print_location(lint)
24
+ print_type(lint)
25
+ print_message(lint)
26
+ end
27
+
28
+ # Prints the location of a lint.
29
+ #
30
+ # @param lint [HamlLint::Lint] the lint to print
31
+ # @return [void]
32
+ def print_location(lint)
33
+ log.info lint.filename, false
34
+ log.log ':', false
35
+ log.bold lint.line, false
36
+ end
37
+
38
+ # Prints the severity of a lint.
39
+ #
40
+ # @param lint [HamlLint::Lint] the lint to print
41
+ # @return [void]
42
+ def print_type(lint)
43
+ message = " [#{lint.severity.mark}] "
44
+
45
+ if lint.error?
46
+ log.error message, false
47
+ else
48
+ log.warning message, false
49
+ end
50
+ end
51
+
52
+ # Prints the description of a lint.
53
+ #
54
+ # @param lint [HamlLint::Lint] the lint to print
55
+ # @return [void]
56
+ def print_message(lint)
57
+ if lint.linter
58
+ log.success("#{lint.linter.name}: ", false)
59
+ end
60
+
61
+ log.log lint.message
62
+ end
63
+
64
+ # Prints a summary of a report when summaries are enabled.
65
+ #
66
+ # @param report [HamlLint::Report] the report to print
67
+ # @return [void]
68
+ def print_summary(report)
69
+ return unless log.summary_enabled
70
+
71
+ print_summary_files(report)
72
+ print_summary_lints(report)
73
+
74
+ log.log ' detected'
75
+ end
76
+
77
+ # Prints a summary of the number of files linted in a report.
78
+ #
79
+ # @param report [HamlLint::Report] the report to print
80
+ # @return [void]
81
+ def print_summary_files(report)
82
+ log.log "\n#{pluralize('file', count: report.files.count)} inspected, ", false
83
+ end
84
+
85
+ # Prints a summary of the number of lints found in a report.
86
+ #
87
+ # @param report [HamlLint::Report] the report to print
88
+ # @return [void]
89
+ def print_summary_lints(report)
90
+ lint_count = report.lints.size
91
+ lint_message = pluralize('lint', count: lint_count)
92
+
93
+ if lint_count == 0
94
+ log.log lint_message, false
95
+ else
96
+ log.error lint_message, false
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,9 +1,13 @@
1
+ require 'haml_lint/reporter/hooks'
2
+
1
3
  module HamlLint
2
4
  # Abstract lint reporter. Subclass and override {#display_report} to
3
5
  # implement a custom lint reporter.
4
6
  #
5
7
  # @abstract
6
8
  class Reporter
9
+ include Reporter::Hooks
10
+
7
11
  # Creates the reporter that will display the given report.
8
12
  #
9
13
  # @param logger [HamlLint::Logger]