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.
- checksums.yaml +4 -4
- data/config/default.yml +18 -0
- data/lib/haml_lint/adapter/haml_4.rb +40 -0
- data/lib/haml_lint/adapter/haml_5.rb +46 -0
- data/lib/haml_lint/adapter.rb +36 -0
- data/lib/haml_lint/cli.rb +12 -9
- data/lib/haml_lint/comment_configuration.rb +39 -0
- data/lib/haml_lint/directive.rb +128 -0
- data/lib/haml_lint/document.rb +3 -1
- data/lib/haml_lint/exceptions.rb +6 -0
- data/lib/haml_lint/haml_visitor.rb +4 -2
- data/lib/haml_lint/lint.rb +2 -2
- data/lib/haml_lint/linter/alignment_tabs.rb +12 -0
- data/lib/haml_lint/linter/consecutive_comments.rb +19 -2
- data/lib/haml_lint/linter/consecutive_silent_scripts.rb +18 -2
- data/lib/haml_lint/linter/final_newline.rb +6 -5
- data/lib/haml_lint/linter/id_names.rb +28 -0
- data/lib/haml_lint/linter/indentation.rb +4 -2
- data/lib/haml_lint/linter/instance_variables.rb +77 -0
- data/lib/haml_lint/linter/line_length.rb +5 -3
- data/lib/haml_lint/linter/repeated_id.rb +34 -0
- data/lib/haml_lint/linter/syntax.rb +6 -0
- data/lib/haml_lint/linter/trailing_whitespace.rb +5 -4
- data/lib/haml_lint/linter.rb +1 -1
- data/lib/haml_lint/logger.rb +7 -1
- data/lib/haml_lint/options.rb +20 -4
- data/lib/haml_lint/parsed_ruby.rb +22 -0
- data/lib/haml_lint/rake_task.rb +16 -2
- data/lib/haml_lint/report.rb +46 -2
- data/lib/haml_lint/reporter/default_reporter.rb +7 -29
- data/lib/haml_lint/reporter/hash_reporter.rb +51 -0
- data/lib/haml_lint/reporter/hooks.rb +25 -0
- data/lib/haml_lint/reporter/json_reporter.rb +2 -45
- data/lib/haml_lint/reporter/progress_reporter.rb +47 -0
- data/lib/haml_lint/reporter/utils.rb +101 -0
- data/lib/haml_lint/reporter.rb +4 -0
- data/lib/haml_lint/runner.rb +70 -10
- data/lib/haml_lint/severity.rb +95 -0
- data/lib/haml_lint/tree/haml_comment_node.rb +18 -0
- data/lib/haml_lint/tree/node.rb +116 -16
- data/lib/haml_lint/tree/null_node.rb +15 -0
- data/lib/haml_lint/tree/root_node.rb +16 -0
- data/lib/haml_lint/tree/script_node.rb +9 -0
- data/lib/haml_lint/tree/silent_script_node.rb +7 -0
- data/lib/haml_lint/tree/tag_node.rb +24 -5
- data/lib/haml_lint/version.rb +1 -1
- data/lib/haml_lint.rb +1 -0
- metadata +41 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b7cae686b1a15d37a81d966b3c80c41fe51fbe2e
|
4
|
+
data.tar.gz: e1997a13d2721bedc442891b8a215e8e825180bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
data/lib/haml_lint/document.rb
CHANGED
@@ -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(
|
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
|
data/lib/haml_lint/exceptions.rb
CHANGED
@@ -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
|
-
|
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)
|
data/lib/haml_lint/lint.rb
CHANGED
@@ -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
|
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
|
8
|
+
def visit_haml_comment(node)
|
9
|
+
return if previously_reported?(node)
|
10
|
+
|
9
11
|
HamlLint::Utils.for_consecutive_items(
|
10
|
-
node
|
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
|
11
|
+
def visit_silent_script(node)
|
12
|
+
return if previously_reported?(node)
|
13
|
+
|
12
14
|
HamlLint::Utils.for_consecutive_items(
|
13
|
-
node
|
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(
|
6
|
+
def visit_root(root)
|
7
7
|
return if document.source.empty?
|
8
8
|
|
9
|
-
|
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(
|
16
|
+
record_lint(node, 'Files should end with a trailing newline')
|
15
17
|
end
|
16
18
|
elsif ends_with_newline
|
17
|
-
record_lint(
|
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(
|
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
|
-
|
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
|