haml_lint 0.21.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
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]