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
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