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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9343eacc913a5864ba1b9537bffc6bdcd657deaf
4
- data.tar.gz: 1aa917703eea3279812a58dfea38a4f342078bd7
3
+ metadata.gz: b7cae686b1a15d37a81d966b3c80c41fe51fbe2e
4
+ data.tar.gz: e1997a13d2721bedc442891b8a215e8e825180bf
5
5
  SHA512:
6
- metadata.gz: 0915d21c8eb959014d5253dde8a71fc4b24b11146f02a1562abc47c38ec94da67ae829991131e27a029c8f3809cc4c2de1e2e05379c2093530063678ff2583d4
7
- data.tar.gz: a890450c449514de7114891ce762354652ffb1b6d5a9811844906022706f6017a73fc296e7e0fe35622c0565e2e5dde95dc782d0ff544d64044eaa097f5a7e1b
6
+ metadata.gz: 62926eeddd753e8af645a8db5cc621cbf3807362453b6913a5f47060b8e13baaa9a8e4dcaa54f32111bc927c53ad08ccfa3ad71c141150c800aacd50a01afac7
7
+ data.tar.gz: 8c77947ce1117683f9386724b01a9447f3d5964e958026f872c89d0eff1c7cd70455506ce109041d72a2caaaa83c63e52cdbdb46085707adf81dbe6d6f646b51
data/config/default.yml CHANGED
@@ -8,6 +8,9 @@
8
8
  skip_frontmatter: false
9
9
 
10
10
  linters:
11
+ AlignmentTabs:
12
+ enabled: true
13
+
11
14
  AltText:
12
15
  enabled: false
13
16
 
@@ -37,9 +40,20 @@ linters:
37
40
  HtmlAttributes:
38
41
  enabled: true
39
42
 
43
+ IdNames:
44
+ enabled: true
45
+ style: lisp_case
46
+
40
47
  ImplicitDiv:
41
48
  enabled: true
42
49
 
50
+ InstanceVariables:
51
+ enabled: true
52
+ file_types: partials
53
+ matchers:
54
+ all: .*
55
+ partials: \A_.*\.haml\z
56
+
43
57
  LeadingCommentSpace:
44
58
  enabled: true
45
59
 
@@ -56,6 +70,10 @@ linters:
56
70
  ObjectReferenceAttributes:
57
71
  enabled: true
58
72
 
73
+ RepeatedId:
74
+ enabled: true
75
+ severity: error
76
+
59
77
  RuboCop:
60
78
  enabled: true
61
79
  # These cops are incredibly noisy when it comes to HAML templates, so we
@@ -0,0 +1,40 @@
1
+ module HamlLint
2
+ class Adapter
3
+ # Adapts the Haml::Parser from Haml 4 for use in HamlLint
4
+ # :reek:UncommunicativeModuleName
5
+ class Haml4 < Adapter
6
+ extend Forwardable
7
+
8
+ # Parses the specified Haml code into an abstract syntax tree
9
+ #
10
+ # @example
11
+ # HamlLint::Adapter::Haml4.new('%div')
12
+ #
13
+ # @api public
14
+ # @param source [String] Haml code to parse
15
+ # @param options [Haml::Options]
16
+ def initialize(source, options = Haml::Options.new)
17
+ @parser = Haml::Parser.new(source, options)
18
+ end
19
+
20
+ # @!method
21
+ # Parses the source code into an abstract syntax tree
22
+ #
23
+ # @example
24
+ # HamlLint::Adapter::Haml4.new('%div')
25
+ #
26
+ # @api public
27
+ # @return [Haml::Parser::ParseNode]
28
+ # @raise [Haml::Error]
29
+ def_delegator :parser, :parse
30
+
31
+ private
32
+
33
+ # The Haml parser to adapt for HamlLint
34
+ #
35
+ # @api private
36
+ # @return [Haml::Parser] the Haml 4 parser
37
+ attr_reader :parser
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ module HamlLint
2
+ class Adapter
3
+ # Adapts the Haml::Parser from Haml 5 for use in HamlLint
4
+ # :reek:UncommunicativeModuleName
5
+ class Haml5 < Adapter
6
+ # Parses the specified Haml code into an abstract syntax tree
7
+ #
8
+ # @example
9
+ # HamlLint::Adapter::Haml5.new('%div')
10
+ #
11
+ # @api public
12
+ # @param source [String] Haml code to parse
13
+ # @param options [Haml::Options]
14
+ def initialize(source, options = Haml::Options.new)
15
+ @source = source
16
+ @parser = Haml::Parser.new(options)
17
+ end
18
+
19
+ # Parses the source code into an abstract syntax tree
20
+ #
21
+ # @example
22
+ # HamlLint::Adapter::Haml5.new('%div').parse
23
+ #
24
+ # @api public
25
+ # @return [Haml::Parser::ParseNode]
26
+ # @raise [Haml::Error]
27
+ def parse
28
+ parser.call(source)
29
+ end
30
+
31
+ private
32
+
33
+ # The Haml parser to adapt for HamlLint
34
+ #
35
+ # @api private
36
+ # @return [Haml::Parser] the Haml 4 parser
37
+ attr_reader :parser
38
+
39
+ # The Haml code to parse
40
+ #
41
+ # @api private
42
+ # @return [String] Haml code to parse
43
+ attr_reader :source
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,36 @@
1
+ require 'haml_lint/adapter/haml_4'
2
+ require 'haml_lint/adapter/haml_5'
3
+ require 'haml_lint/exceptions'
4
+
5
+ module HamlLint
6
+ # Determines the adapter to use for the current Haml version
7
+ class Adapter
8
+ # Detects the adapter to use for the current Haml version
9
+ #
10
+ # @example
11
+ # HamlLint::Adapter.detect_class.new('%div')
12
+ #
13
+ # @api public
14
+ # @return [Class] the adapter class
15
+ # @raise [HamlLint::Exceptions::UnknownHamlVersion]
16
+ def self.detect_class
17
+ version = haml_version
18
+ case version
19
+ when '~> 4.0' then HamlLint::Adapter::Haml4
20
+ when '~> 5.0' then HamlLint::Adapter::Haml5
21
+ else fail HamlLint::Exceptions::UnknownHamlVersion, "Cannot handle Haml version: #{version}"
22
+ end
23
+ end
24
+
25
+ # Determines the approximate version of Haml that is installed
26
+ #
27
+ # @api private
28
+ # @return [String] the approximate Haml version
29
+ def self.haml_version
30
+ Gem::Version
31
+ .new(Haml::VERSION)
32
+ .approximate_recommendation
33
+ end
34
+ private_class_method :haml_version
35
+ end
36
+ end
data/lib/haml_lint/cli.rb CHANGED
@@ -33,7 +33,7 @@ module HamlLint
33
33
  #
34
34
  # @return [Integer] exit status code
35
35
  def act_on_options(options)
36
- log.color_enabled = options.fetch(:color, log.tty?)
36
+ configure_logger(options)
37
37
 
38
38
  if options[:help]
39
39
  print_help(options)
@@ -52,6 +52,14 @@ module HamlLint
52
52
  end
53
53
  end
54
54
 
55
+ # Given the provided options, configure the logger.
56
+ #
57
+ # @return [void]
58
+ def configure_logger(options)
59
+ log.color_enabled = options.fetch(:color, log.tty?)
60
+ log.summary_enabled = options.fetch(:summary, true)
61
+ end
62
+
55
63
  # Outputs a message and returns an appropriate error code for the specified
56
64
  # exception.
57
65
  def handle_exception(ex)
@@ -79,16 +87,11 @@ module HamlLint
79
87
  #
80
88
  # @return [Integer] exit status code
81
89
  def scan_for_lints(options)
82
- report = Runner.new.run(options)
83
- print_report(report, options)
84
- report.failed? ? Sysexits::EX_DATAERR : Sysexits::EX_OK
85
- end
86
-
87
- # Outputs a report of the linter run using the specified reporter.
88
- def print_report(report, options)
89
90
  reporter = options.fetch(:reporter,
90
91
  HamlLint::Reporter::DefaultReporter).new(log)
91
- reporter.display_report(report)
92
+ report = Runner.new.run(options.merge(reporter: reporter))
93
+ report.display
94
+ report.failed? ? Sysexits::EX_DATAERR : Sysexits::EX_OK
92
95
  end
93
96
 
94
97
  # Outputs a list of all currently available linters.
@@ -0,0 +1,39 @@
1
+ module HamlLint
2
+ # Determines what linters are enabled or disabled via comments.
3
+ class CommentConfiguration
4
+ # Instantiates a new {HamlLint::CommentConfiguration}.
5
+ #
6
+ # @param node [HamlLint::Tree::Node] the node to configure
7
+ def initialize(node)
8
+ @directives = node.directives.reverse
9
+ end
10
+
11
+ # Checks whether a linter is disabled for the node.
12
+ #
13
+ # @api public
14
+ # @param linter_name [String] the name of the linter
15
+ # @return [true, false]
16
+ def disabled?(linter_name)
17
+ most_recent_disabled = directives_for(linter_name).map(&:disable?).first
18
+
19
+ most_recent_disabled || false
20
+ end
21
+
22
+ private
23
+
24
+ # The list of directives in order of precedence.
25
+ #
26
+ # @api private
27
+ # @return [Array<HamlLint::Directive>]
28
+ attr_reader :directives
29
+
30
+ # Finds all directives applicable to the given linter name.
31
+ #
32
+ # @api private
33
+ # @param linter_name [String] the name of the linter
34
+ # @return [Array<HamlLint::Directive>] the filtered directives
35
+ def directives_for(linter_name)
36
+ directives.select { |directive| (directive.linters & ['all', linter_name]).any? }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,128 @@
1
+ module HamlLint
2
+ # Handles linter configuration transformation via Haml comments.
3
+ class Directive
4
+ LINTER_REGEXP = /(?:[A-Z]\w+)/
5
+
6
+ DIRECTIVE_REGEXP = /
7
+ # "haml-lint:" with optional spacing
8
+ \s*haml-lint\s*:\s*
9
+
10
+ # The mode - either disable or enable
11
+ (?<mode>(?:dis|en)able)\b\s*
12
+
13
+ # "all" or a comma-separated list (with optional spaces) of linters
14
+ (?<linters>all | (?:#{LINTER_REGEXP}\s*,\s*)* #{LINTER_REGEXP})
15
+ /x
16
+
17
+ # Constructs a directive from source code as a given line.
18
+ #
19
+ # @param source [String] the source code to analyze
20
+ # @param line [Integer] the line number the source starts at
21
+ # @return [HamlLint::Directive]
22
+ def self.from_line(source, line)
23
+ match = DIRECTIVE_REGEXP.match(source)
24
+
25
+ if match
26
+ new(source, line, match[:mode], match[:linters].split(/\s*,\s*/))
27
+ else
28
+ Null.new(source, line)
29
+ end
30
+ end
31
+
32
+ # Instantiates a new {HamlLint::Directive}
33
+ #
34
+ # @api semipublic
35
+ # @param source [String] the source code to analyze
36
+ # @param line [Integer] the line number the source starts at
37
+ # @param mode [String] the type of directive, one of "disable" or "enable"
38
+ # @param linters [Array<String>] the name of the linters to act upon
39
+ def initialize(source, line, mode, linters)
40
+ @source = source
41
+ @line = line
42
+ @mode = mode
43
+ @linters = linters
44
+ end
45
+
46
+ # The names of the linters to act upon.
47
+ #
48
+ # @return [String]
49
+ attr_reader :linters
50
+
51
+ # The mode of the directive. One of "disable" or "enable".
52
+ #
53
+ # @return [String]
54
+ attr_reader :mode
55
+
56
+ # Checks whether a directive is equivalent to another.
57
+ #
58
+ # @api public
59
+ # @param other [HamlLint::Directive] the other directive
60
+ # @return [true, false]
61
+ def ==(other)
62
+ super unless other.is_a?(HamlLint::Directive)
63
+
64
+ mode == other.mode && linters == other.linters
65
+ end
66
+
67
+ # Checks whether this is a disable directive.
68
+ #
69
+ # @return [true, false]
70
+ def disable?
71
+ mode == 'disable'
72
+ end
73
+
74
+ # Checks whether this is an enable directive.
75
+ #
76
+ # @return [true, false]
77
+ def enable?
78
+ mode == 'enable'
79
+ end
80
+
81
+ # Formats the directive for display in a console.
82
+ #
83
+ # @return [String]
84
+ def inspect
85
+ "#<HamlLint::Directive(mode=#{mode}, linters=#{linters})>"
86
+ end
87
+
88
+ # A null representation of a directive.
89
+ class Null < Directive
90
+ # Instantiates a new null directive.
91
+ #
92
+ # @param source [String] the source code to analyze
93
+ # @param line [Integer] the line number the source starts at
94
+ def initialize(source, line)
95
+ @source = source
96
+ @line = line
97
+ end
98
+
99
+ # Stubs out the disable check as false.
100
+ #
101
+ # @return [false]
102
+ def disable?
103
+ false
104
+ end
105
+
106
+ # Stubs out the ensable check as false.
107
+ #
108
+ # @return [false]
109
+ def enable?
110
+ false
111
+ end
112
+
113
+ # Formats the null directive for display in a console.
114
+ #
115
+ # @return [String]
116
+ def inspect
117
+ '#<HamlLint::Directive::Null>'
118
+ end
119
+
120
+ # Stubs out the linters.
121
+ #
122
+ # @return [Array]
123
+ def linters
124
+ []
125
+ end
126
+ end
127
+ end
128
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'haml_lint/adapter'
4
+
3
5
  module HamlLint
4
6
  # Represents a parsed Haml document and its associated metadata.
5
7
  class Document
@@ -43,7 +45,7 @@ module HamlLint
43
45
  @source = strip_frontmatter(source)
44
46
  @source_lines = @source.split("\n")
45
47
 
46
- @tree = process_tree(Haml::Parser.new(@source, Haml::Options.new).parse)
48
+ @tree = process_tree(HamlLint::Adapter.detect_class.new(@source).parse)
47
49
  rescue Haml::Error => ex
48
50
  error = HamlLint::Exceptions::ParseError.new(ex.message, ex.line)
49
51
  raise error
@@ -15,4 +15,10 @@ module HamlLint::Exceptions
15
15
  # Raised when attempting to execute `Runner` with options that would result in
16
16
  # no linters being enabled.
17
17
  class NoLintersError < StandardError; end
18
+
19
+ # Raised when an unsupported Haml version is detected
20
+ class UnknownHamlVersion < StandardError; end
21
+
22
+ # Raised when a severity is not recognized
23
+ class UnknownSeverity < StandardError; end
18
24
  end
@@ -14,14 +14,16 @@ module HamlLint
14
14
  visit_children(node) if descend == :children
15
15
  end
16
16
 
17
- safe_send("visit_#{node_name(node)}", node, &block)
17
+ disabled = node.disabled?(self)
18
+
19
+ safe_send("visit_#{node_name(node)}", node, &block) unless disabled
18
20
 
19
21
  # Visit all children by default unless the block was invoked (indicating
20
22
  # the user intends to not recurse further, or wanted full control over
21
23
  # when the children were visited).
22
24
  visit_children(node) unless block_called
23
25
 
24
- safe_send("after_visit_#{node_name(node)}", node, &block)
26
+ safe_send("after_visit_#{node_name(node)}", node, &block) unless disabled
25
27
  end
26
28
 
27
29
  def visit_children(parent)
@@ -28,14 +28,14 @@ module HamlLint
28
28
  @filename = filename
29
29
  @line = line || 0
30
30
  @message = message
31
- @severity = severity
31
+ @severity = Severity.new(severity)
32
32
  end
33
33
 
34
34
  # Return whether this lint has a severity of error.
35
35
  #
36
36
  # @return [Boolean]
37
37
  def error?
38
- @severity == :error
38
+ @severity.error?
39
39
  end
40
40
  end
41
41
  end
@@ -0,0 +1,12 @@
1
+ module HamlLint
2
+ # Checks for tabs that are placed for alignment of tag content
3
+ class Linter::AlignmentTabs < Linter
4
+ REGEX = /[^\s*]\t+/
5
+
6
+ def visit_tag(node)
7
+ if REGEX.match(node.source_code)
8
+ record_lint(node, 'Avoid using tabs for alignment')
9
+ end
10
+ end
11
+ end
12
+ end
@@ -5,14 +5,31 @@ module HamlLint
5
5
 
6
6
  COMMENT_DETECTOR = ->(child) { child.type == :haml_comment }
7
7
 
8
- def visit_root(node)
8
+ def visit_haml_comment(node)
9
+ return if previously_reported?(node)
10
+
9
11
  HamlLint::Utils.for_consecutive_items(
10
- node.children,
12
+ possible_group(node),
11
13
  COMMENT_DETECTOR,
12
14
  ) do |group|
15
+ group.each { |group_node| reported_nodes << group_node }
13
16
  record_lint(group.first,
14
17
  "#{group.count} consecutive comments can be merged into one")
15
18
  end
16
19
  end
20
+
21
+ private
22
+
23
+ def possible_group(node)
24
+ node.subsequents.unshift(node)
25
+ end
26
+
27
+ def previously_reported?(node)
28
+ reported_nodes.include?(node)
29
+ end
30
+
31
+ def reported_nodes
32
+ @reported_nodes ||= []
33
+ end
17
34
  end
18
35
  end
@@ -8,9 +8,11 @@ module HamlLint
8
8
  child.type == :silent_script && child.children.empty?
9
9
  end
10
10
 
11
- def visit_root(node)
11
+ def visit_silent_script(node)
12
+ return if previously_reported?(node)
13
+
12
14
  HamlLint::Utils.for_consecutive_items(
13
- node.children,
15
+ possible_group(node),
14
16
  SILENT_SCRIPT_DETECTOR,
15
17
  config['max_consecutive'] + 1,
16
18
  ) do |group|
@@ -19,5 +21,19 @@ module HamlLint
19
21
  'into a single `:ruby` filter')
20
22
  end
21
23
  end
24
+
25
+ private
26
+
27
+ def possible_group(node)
28
+ node.subsequents.unshift(node)
29
+ end
30
+
31
+ def previously_reported?(node)
32
+ reported_nodes.include?(node)
33
+ end
34
+
35
+ def reported_nodes
36
+ @reported_nodes ||= []
37
+ end
22
38
  end
23
39
  end
@@ -3,19 +3,20 @@ module HamlLint
3
3
  class Linter::FinalNewline < Linter
4
4
  include LinterRegistry
5
5
 
6
- def visit_root(_node)
6
+ def visit_root(root)
7
7
  return if document.source.empty?
8
8
 
9
- dummy_node = Struct.new(:line).new(document.source_lines.count)
9
+ node = root.node_for_line(document.source_lines.count)
10
+ return if node.disabled?(self)
11
+
10
12
  ends_with_newline = document.source.end_with?("\n")
11
13
 
12
14
  if config['present']
13
15
  unless ends_with_newline
14
- record_lint(dummy_node, 'Files should end with a trailing newline')
16
+ record_lint(node, 'Files should end with a trailing newline')
15
17
  end
16
18
  elsif ends_with_newline
17
- record_lint(dummy_node,
18
- 'Files should not end with a trailing newline')
19
+ record_lint(node, 'Files should not end with a trailing newline')
19
20
  end
20
21
  end
21
22
  end
@@ -0,0 +1,28 @@
1
+ module HamlLint
2
+ # Checks for `id` attributes in specific cases on tags.
3
+ class Linter::IdNames < Linter
4
+ include LinterRegistry
5
+
6
+ STYLIZED_NAMES = {
7
+ 'camel_case' => 'camelCase',
8
+ 'lisp_case' => 'lisp-case',
9
+ 'pascal_case' => 'PascalCase',
10
+ 'snake_case' => 'snake_case',
11
+ }.freeze
12
+
13
+ STYLES = {
14
+ 'camel_case' => /\A[a-z][\da-zA-Z]+\z/,
15
+ 'lisp_case' => /\A[\da-z-]+\z/,
16
+ 'pascal_case' => /\A[A-Z][\da-zA-Z]+\z/,
17
+ 'snake_case' => /\A[\da-z_]+\z/,
18
+ }.freeze
19
+
20
+ def visit_tag(node)
21
+ return unless (id = node.tag_id)
22
+
23
+ style = config['style'] || 'lisp_case'
24
+ matcher = STYLES[style]
25
+ record_lint(node, "`id` attribute must be in #{STYLIZED_NAMES[style]}") unless id =~ matcher
26
+ end
27
+ end
28
+ end
@@ -9,14 +9,16 @@ module HamlLint
9
9
  tab: /^\t*(?![ ])/,
10
10
  }.freeze
11
11
 
12
- def visit_root(_node)
12
+ def visit_root(root)
13
13
  regex = INDENT_REGEX[config['character'].to_sym]
14
14
  dummy_node = Struct.new(:line)
15
15
 
16
16
  document.source_lines.each_with_index do |line, index|
17
17
  next if line =~ regex
18
18
 
19
- record_lint dummy_node.new(index + 1), 'Line contains tabs in indentation'
19
+ unless root.node_for_line(index).disabled?(self)
20
+ record_lint dummy_node.new(index + 1), 'Line contains tabs in indentation'
21
+ end
20
22
  end
21
23
  end
22
24
  end
@@ -0,0 +1,77 @@
1
+ module HamlLint
2
+ # Checks for the presence of instance variables
3
+ class Linter::InstanceVariables < Linter
4
+ include LinterRegistry
5
+
6
+ # Enables the linter if the tree is for the right file type.
7
+ #
8
+ # @param [HamlLint::Tree::RootNode] the root of a syntax tree
9
+ # @return [true, false] whether the linter is enabled for the tree
10
+ def visit_root(node)
11
+ @enabled = matcher.match(File.basename(node.file)) ? true : false
12
+ end
13
+
14
+ # Checks for instance variables in script nodes when the linter is enabled.
15
+ #
16
+ # @param [HamlLint::Tree:ScriptNode]
17
+ # @return [void]
18
+ def visit_script(node)
19
+ return unless enabled?
20
+
21
+ if node.parsed_script.contains_instance_variables?
22
+ record_lint(node, "Avoid using instance variables in #{file_types} views")
23
+ end
24
+ end
25
+
26
+ # @!method visit_silent_script(node)
27
+ # Checks for instance variables in script nodes when the linter is enabled.
28
+ #
29
+ # @param [HamlLint::Tree:SilentScriptNode]
30
+ # @return [void]
31
+ alias visit_silent_script visit_script
32
+
33
+ # Checks for instance variables in tag nodes when the linter is enabled.
34
+ #
35
+ # @param [HamlLint::Tree:TagNode]
36
+ # @return [void]
37
+ def visit_tag(node)
38
+ return unless enabled?
39
+
40
+ visit_script(node) ||
41
+ if node.parsed_attributes.contains_instance_variables?
42
+ record_lint(node, "Avoid using instance variables in #{file_types} views")
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Tracks whether the linter is enabled for the file.
49
+ #
50
+ # @api private
51
+ # @return [true, false]
52
+ attr_reader :enabled
53
+
54
+ # @!method enabled?
55
+ # Checks whether the linter is enabled for the file.
56
+ #
57
+ # @api private
58
+ # @return [true, false]
59
+ alias enabled? enabled
60
+
61
+ # The type of files the linter is configured to check.
62
+ #
63
+ # @api private
64
+ # @return [String]
65
+ def file_types
66
+ @file_types ||= config['file_types'] || 'partial'
67
+ end
68
+
69
+ # The matcher to use for testing whether to check a file by file name.
70
+ #
71
+ # @api private
72
+ # @return [Regexp]
73
+ def matcher
74
+ @matcher ||= Regexp.new(config['matchers'][file_types] || '\A_.*\.haml\z')
75
+ end
76
+ end
77
+ end