scss_lint 0.42.2 → 0.43.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 +9 -1
- data/data/properties.txt +1 -1
- data/lib/scss_lint/cli.rb +11 -5
- data/lib/scss_lint/config.rb +1 -2
- data/lib/scss_lint/control_comment_processor.rb +33 -28
- data/lib/scss_lint/engine.rb +10 -7
- data/lib/scss_lint/linter.rb +56 -12
- data/lib/scss_lint/linter/chained_classes.rb +21 -0
- data/lib/scss_lint/linter/color_variable.rb +0 -7
- data/lib/scss_lint/linter/comment.rb +15 -1
- data/lib/scss_lint/linter/empty_line_between_blocks.rb +10 -0
- data/lib/scss_lint/linter/indentation.rb +47 -53
- data/lib/scss_lint/linter/mergeable_selector.rb +29 -0
- data/lib/scss_lint/linter/property_spelling.rb +18 -6
- data/lib/scss_lint/linter/pseudo_element.rb +18 -0
- data/lib/scss_lint/linter/single_line_per_selector.rb +23 -7
- data/lib/scss_lint/linter/space_after_property_colon.rb +5 -6
- data/lib/scss_lint/linter/space_after_property_name.rb +1 -7
- data/lib/scss_lint/linter/space_after_variable_name.rb +1 -1
- data/lib/scss_lint/linter/space_around_operator.rb +23 -7
- data/lib/scss_lint/linter/string_quotes.rb +2 -9
- data/lib/scss_lint/linter/trailing_semicolon.rb +10 -0
- data/lib/scss_lint/options.rb +5 -0
- data/lib/scss_lint/runner.rb +10 -8
- data/lib/scss_lint/selector_visitor.rb +11 -3
- data/lib/scss_lint/version.rb +1 -1
- data/spec/scss_lint/cli_spec.rb +14 -0
- data/spec/scss_lint/linter/chained_classes_spec.rb +45 -0
- data/spec/scss_lint/linter/comment_spec.rb +16 -0
- data/spec/scss_lint/linter/empty_line_between_blocks_spec.rb +62 -0
- data/spec/scss_lint/linter/indentation_spec.rb +1 -9
- data/spec/scss_lint/linter/leading_zero_spec.rb +12 -0
- data/spec/scss_lint/linter/mergeable_selector_spec.rb +59 -0
- data/spec/scss_lint/linter/property_spelling_spec.rb +28 -0
- data/spec/scss_lint/linter/pseudo_element_spec.rb +71 -0
- data/spec/scss_lint/linter/single_line_per_selector_spec.rb +28 -1
- data/spec/scss_lint/linter/space_after_variable_name_spec.rb +12 -0
- data/spec/scss_lint/linter/space_around_operator_spec.rb +51 -0
- data/spec/scss_lint/linter/trailing_semicolon_spec.rb +28 -0
- data/spec/scss_lint/linter_spec.rb +17 -0
- data/spec/scss_lint/runner_spec.rb +2 -2
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26102783a3fa8f2e4ade260a8a483aa2d2a315f6
|
4
|
+
data.tar.gz: ce8673ca36772ff3505bb6ce3eec4861f78ebed4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5558f7bbe06908b1901d949215856aad7c0318a4fb10305ff89e3dbea00b08a73c1dcd5edece21898b37cf331585e37c02429ec66337a3a5a887365fa151a18
|
7
|
+
data.tar.gz: 08b6257a9482c94ee656debfc1b141a0c557dccdb970bec4df3588f6ec69dab8888e9a282ab8f74a580f783c81844136f2c729c034a32703dd756873a2a4d0a7
|
data/config/default.yml
CHANGED
@@ -21,6 +21,9 @@ linters:
|
|
21
21
|
enabled: true
|
22
22
|
convention: zero # or `none`
|
23
23
|
|
24
|
+
ChainedClasses:
|
25
|
+
enabled: false
|
26
|
+
|
24
27
|
ColorKeyword:
|
25
28
|
enabled: true
|
26
29
|
|
@@ -29,6 +32,7 @@ linters:
|
|
29
32
|
|
30
33
|
Comment:
|
31
34
|
enabled: true
|
35
|
+
style: silent
|
32
36
|
|
33
37
|
DebugStatement:
|
34
38
|
enabled: true
|
@@ -123,6 +127,7 @@ linters:
|
|
123
127
|
PropertySpelling:
|
124
128
|
enabled: true
|
125
129
|
extra_properties: []
|
130
|
+
disabled_properties: []
|
126
131
|
|
127
132
|
PropertyUnits:
|
128
133
|
enabled: true
|
@@ -137,6 +142,9 @@ linters:
|
|
137
142
|
'%'] # Other
|
138
143
|
properties: {}
|
139
144
|
|
145
|
+
PseudoElement:
|
146
|
+
enabled: true
|
147
|
+
|
140
148
|
QualifyingElement:
|
141
149
|
enabled: true
|
142
150
|
allow_element_with_attribute: false
|
@@ -178,7 +186,7 @@ linters:
|
|
178
186
|
|
179
187
|
SpaceAroundOperator:
|
180
188
|
enabled: true
|
181
|
-
style: one_space # or 'no_space'
|
189
|
+
style: one_space # or 'at_least_one_space', or 'no_space'
|
182
190
|
|
183
191
|
SpaceBeforeBrace:
|
184
192
|
enabled: true
|
data/data/properties.txt
CHANGED
data/lib/scss_lint/cli.rb
CHANGED
@@ -54,8 +54,16 @@ module SCSSLint
|
|
54
54
|
|
55
55
|
def scan_for_lints(options, config)
|
56
56
|
runner = Runner.new(config)
|
57
|
-
|
58
|
-
|
57
|
+
files =
|
58
|
+
if options[:stdin_file_path]
|
59
|
+
[{ file: STDIN, path: options[:stdin_file_path] }]
|
60
|
+
else
|
61
|
+
FileFinder.new(config).find(options[:files]).map do |file_path|
|
62
|
+
{ path: file_path }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
runner.run(files)
|
66
|
+
report_lints(options, runner.lints, files)
|
59
67
|
|
60
68
|
if runner.lints.any?(&:error?)
|
61
69
|
halt :error
|
@@ -203,9 +211,7 @@ module SCSSLint
|
|
203
211
|
def print_linters
|
204
212
|
puts 'Installed linters:'
|
205
213
|
|
206
|
-
linter_names = LinterRegistry.linters.map
|
207
|
-
linter.name.split('::').last
|
208
|
-
end
|
214
|
+
linter_names = LinterRegistry.linters.map(&:simple_name)
|
209
215
|
|
210
216
|
linter_names.sort.each do |linter_name|
|
211
217
|
puts " - #{linter_name}"
|
data/lib/scss_lint/config.rb
CHANGED
@@ -20,19 +20,21 @@ module SCSSLint
|
|
20
20
|
#
|
21
21
|
# @param node [Sass::Tree::Node]
|
22
22
|
def before_node_visit(node)
|
23
|
-
return unless
|
23
|
+
return unless (commands = Array(extract_commands(node))).any?
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
commands.each do |command|
|
26
|
+
linters = command[:linters]
|
27
|
+
next unless linters.include?('all') || linters.include?(@linter.name)
|
27
28
|
|
28
|
-
|
29
|
+
process_command(command, node)
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
# Is the control comment the only thing on this line?
|
32
|
+
next if node.is_a?(Sass::Tree::RuleNode) ||
|
33
|
+
%r{^\s*(//|/\*)}.match(@linter.engine.lines[command[:line] - 1])
|
33
34
|
|
34
|
-
|
35
|
-
|
35
|
+
# Otherwise, pop since we only want comment to apply to the single line
|
36
|
+
pop_control_comment_stack(node)
|
37
|
+
end
|
36
38
|
end
|
37
39
|
|
38
40
|
# Executed after a node has been visited.
|
@@ -46,35 +48,38 @@ module SCSSLint
|
|
46
48
|
|
47
49
|
private
|
48
50
|
|
49
|
-
def
|
51
|
+
def extract_commands(node)
|
50
52
|
return unless comment = retrieve_comment_text(node)
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
scss-lint:
|
54
|
+
commands = []
|
55
|
+
comment.split("\n").each_with_index do |comment_line, line_no|
|
56
|
+
next unless match = %r{
|
57
|
+
//\s*scss-lint:
|
56
58
|
(?<action>disable|enable)\s+
|
57
59
|
(?<linters>.*?)
|
58
|
-
\s*(
|
60
|
+
\s*($|\*\/) # End of line
|
59
61
|
}x.match(comment_line)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
|
63
|
+
commands << {
|
64
|
+
action: match[:action],
|
65
|
+
linters: match[:linters].split(/\s*,\s*|\s+/),
|
66
|
+
line: node.line + line_no
|
67
|
+
}
|
66
68
|
end
|
67
69
|
|
68
|
-
|
70
|
+
commands
|
69
71
|
end
|
70
72
|
|
71
73
|
def retrieve_comment_text(node)
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
74
|
+
text_with_markers =
|
75
|
+
case node
|
76
|
+
when Sass::Tree::CommentNode
|
77
|
+
node.value.first
|
78
|
+
when Sass::Tree::RuleNode
|
79
|
+
node.rule.select { |chunk| chunk.is_a?(String) }.join
|
80
|
+
end
|
81
|
+
|
82
|
+
text_with_markers.gsub(%r{\A/\*}, '//').gsub(/\n \*/, "\n//") if text_with_markers
|
78
83
|
end
|
79
84
|
|
80
85
|
def process_command(command, node)
|
data/lib/scss_lint/engine.rb
CHANGED
@@ -17,8 +17,8 @@ module SCSSLint
|
|
17
17
|
# @option options [String] :file The file to load
|
18
18
|
# @option options [String] :code The code to parse
|
19
19
|
def initialize(options = {})
|
20
|
-
if options[:
|
21
|
-
build_from_file(options
|
20
|
+
if options[:path]
|
21
|
+
build_from_file(options)
|
22
22
|
elsif options[:code]
|
23
23
|
build_from_string(options[:code])
|
24
24
|
end
|
@@ -41,11 +41,14 @@ module SCSSLint
|
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
-
# @param
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
# @param options [Hash]
|
45
|
+
# @option file [IO] if provided, us this as the file object
|
46
|
+
# @option path [String] path of file, loading from this if `file` object not
|
47
|
+
# given
|
48
|
+
def build_from_file(options)
|
49
|
+
@filename = options[:path]
|
50
|
+
@contents = options[:file] ? file.read : File.read(@filename)
|
51
|
+
@engine = Sass::Engine.new(@contents, ENGINE_OPTIONS.merge(filename: @filename))
|
49
52
|
end
|
50
53
|
|
51
54
|
# @param scss [String]
|
data/lib/scss_lint/linter.rb
CHANGED
@@ -1,9 +1,25 @@
|
|
1
1
|
module SCSSLint
|
2
2
|
# Defines common functionality available to all linters.
|
3
|
-
class Linter < Sass::Tree::Visitors::Base
|
3
|
+
class Linter < Sass::Tree::Visitors::Base # rubocop:disable ClassLength
|
4
4
|
include SelectorVisitor
|
5
5
|
include Utils
|
6
6
|
|
7
|
+
class << self
|
8
|
+
attr_accessor :simple_name
|
9
|
+
|
10
|
+
# When defining a Linter class, define its simple name as well. This
|
11
|
+
# assumes that the module hierarchy of every linter starts with
|
12
|
+
# `SCSSLint::Linter::`, and removes this part of the class name.
|
13
|
+
#
|
14
|
+
# `SCSSLint::Linter::Foo.simple_name` #=> "Foo"
|
15
|
+
# `SCSSLint::Linter::Compass::Bar.simple_name` #=> "Compass::Bar"
|
16
|
+
def inherited(linter)
|
17
|
+
name_parts = linter.name.split('::')
|
18
|
+
name = name_parts.length < 3 ? '' : name_parts[2..-1].join('::')
|
19
|
+
linter.simple_name = name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
7
23
|
attr_reader :config, :engine, :lints
|
8
24
|
|
9
25
|
# Create a linter.
|
@@ -29,7 +45,7 @@ module SCSSLint
|
|
29
45
|
# Return the human-friendly name of this linter as specified in the
|
30
46
|
# configuration file and in lint descriptions.
|
31
47
|
def name
|
32
|
-
self.class.
|
48
|
+
self.class.simple_name
|
33
49
|
end
|
34
50
|
|
35
51
|
protected
|
@@ -61,16 +77,6 @@ module SCSSLint
|
|
61
77
|
Location.new(range.start_pos.line, range.start_pos.offset, length)
|
62
78
|
end
|
63
79
|
|
64
|
-
# @param source_position [Sass::Source::Position]
|
65
|
-
# @param offset [Integer]
|
66
|
-
# @return [String] the character at the given [Sass::Source::Position]
|
67
|
-
def character_at(source_position, offset = 0)
|
68
|
-
actual_line = source_position.line - 1
|
69
|
-
actual_offset = source_position.offset + offset - 1
|
70
|
-
|
71
|
-
engine.lines.size > actual_line && engine.lines[actual_line][actual_offset]
|
72
|
-
end
|
73
|
-
|
74
80
|
# Extracts the original source code given a range.
|
75
81
|
#
|
76
82
|
# @param source_range [Sass::Source::Range]
|
@@ -145,6 +151,16 @@ module SCSSLint
|
|
145
151
|
|
146
152
|
private
|
147
153
|
|
154
|
+
def visit_comment(_node)
|
155
|
+
# Don't lint children of comments by default, as the Sass parser contains
|
156
|
+
# many bugs related to the source ranges reported within code in /*...*/
|
157
|
+
# comments.
|
158
|
+
#
|
159
|
+
# Instead of defining this empty method on every linter, we assume every
|
160
|
+
# linter ignores comments by default. Individual linters can override at
|
161
|
+
# their discretion.
|
162
|
+
end
|
163
|
+
|
148
164
|
def extract_location(node_or_line_or_location)
|
149
165
|
if node_or_line_or_location.is_a?(Location)
|
150
166
|
node_or_line_or_location
|
@@ -157,5 +173,33 @@ module SCSSLint
|
|
157
173
|
Location.new(node_or_line_or_location)
|
158
174
|
end
|
159
175
|
end
|
176
|
+
|
177
|
+
# @param source_position [Sass::Source::Position]
|
178
|
+
# @param offset [Integer]
|
179
|
+
# @return [String] the character at the given [Sass::Source::Position]
|
180
|
+
def character_at(source_position, offset = 0)
|
181
|
+
actual_line = source_position.line - 1
|
182
|
+
actual_offset = source_position.offset + offset - 1
|
183
|
+
|
184
|
+
engine.lines.size > actual_line && engine.lines[actual_line][actual_offset]
|
185
|
+
end
|
186
|
+
|
187
|
+
# Starting at source_position (plus offset), search for pattern and return
|
188
|
+
# the offset from the source_position.
|
189
|
+
#
|
190
|
+
# @param source_position [Sass::Source::Position]
|
191
|
+
# @param pattern [String, RegExp] the pattern to search for
|
192
|
+
# @param offset [Integer]
|
193
|
+
# @return [Integer] the offset at which [pattern] was found.
|
194
|
+
def offset_to(source_position, pattern, offset = 0)
|
195
|
+
actual_line = source_position.line - 1
|
196
|
+
actual_offset = source_position.offset + offset - 1
|
197
|
+
|
198
|
+
return nil if actual_line >= engine.lines.size
|
199
|
+
|
200
|
+
actual_index = engine.lines[actual_line].index(pattern, actual_offset)
|
201
|
+
|
202
|
+
actual_index && actual_index + 1 - source_position.offset
|
203
|
+
end
|
160
204
|
end
|
161
205
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SCSSLint
|
2
|
+
# Checks for uses of chained classes (e.g. .foo.bar).
|
3
|
+
class Linter::ChainedClasses < Linter
|
4
|
+
include LinterRegistry
|
5
|
+
|
6
|
+
def visit_sequence(sequence)
|
7
|
+
sequence.members.each do |simple_sequence|
|
8
|
+
next unless chained_class?(simple_sequence)
|
9
|
+
add_lint(simple_sequence.line,
|
10
|
+
'Prefer using a distinct class over chained classes ' \
|
11
|
+
'(e.g. .new-class over .foo.bar')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def chained_class?(simple_sequence)
|
18
|
+
simple_sequence.members.count { |member| member.is_a?(Sass::Selector::Class) } >= 2
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -25,13 +25,6 @@ module SCSSLint
|
|
25
25
|
.each { |_, color| record_lint(node, color) }
|
26
26
|
end
|
27
27
|
|
28
|
-
def visit_comment(_node)
|
29
|
-
# Don't lint children. Sass multiline comments (/*...*/) are actually
|
30
|
-
# rendered in code and thus allow variable interpolation. Unfortunately,
|
31
|
-
# the Sass parser returns bad source ranges for interpolation in these
|
32
|
-
# comments, so it's easiest to just ignore them.
|
33
|
-
end
|
34
|
-
|
35
28
|
def visit_script_funcall(node)
|
36
29
|
if literal_color_function?(node)
|
37
30
|
record_lint node, node.to_sass
|
@@ -4,11 +4,25 @@ module SCSSLint
|
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
6
|
def visit_comment(node)
|
7
|
-
add_lint(node, 'Use `//` comments everywhere') unless
|
7
|
+
add_lint(node, 'Use `//` comments everywhere') unless valid_comment?(node)
|
8
8
|
end
|
9
9
|
|
10
10
|
private
|
11
11
|
|
12
|
+
def valid_comment?(node)
|
13
|
+
allowed_type =
|
14
|
+
if config.fetch('style', 'silent') == 'silent'
|
15
|
+
node.invisible?
|
16
|
+
else
|
17
|
+
!node.invisible?
|
18
|
+
end
|
19
|
+
return true if allowed_type
|
20
|
+
|
21
|
+
# Otherwise check if comment contains content that excludes it (i.e. a
|
22
|
+
# copyright notice for loud comments)
|
23
|
+
allowed?(node)
|
24
|
+
end
|
25
|
+
|
12
26
|
# @param node [CommentNode]
|
13
27
|
# @return [Boolean]
|
14
28
|
def allowed?(node)
|
@@ -3,11 +3,21 @@ module SCSSLint
|
|
3
3
|
class Linter::EmptyLineBetweenBlocks < Linter
|
4
4
|
include LinterRegistry
|
5
5
|
|
6
|
+
def visit_atroot(node)
|
7
|
+
check(node, '@at-root')
|
8
|
+
yield
|
9
|
+
end
|
10
|
+
|
6
11
|
def visit_function(node)
|
7
12
|
check(node, '@function')
|
8
13
|
yield
|
9
14
|
end
|
10
15
|
|
16
|
+
def visit_media(node)
|
17
|
+
check(node, '@media')
|
18
|
+
yield
|
19
|
+
end
|
20
|
+
|
11
21
|
def visit_mixin(node)
|
12
22
|
# Ignore @includes which don't have any block content
|
13
23
|
check(node, '@include') if node.children
|
@@ -6,7 +6,16 @@ module SCSSLint
|
|
6
6
|
def visit_root(_node)
|
7
7
|
@indent_width = config['width'].to_i
|
8
8
|
@indent_character = config['character'] || 'space'
|
9
|
+
if @indent_character == 'tab'
|
10
|
+
@other_character = ' '
|
11
|
+
@other_character_name = 'space'
|
12
|
+
else
|
13
|
+
@other_character = "\t"
|
14
|
+
@other_character_name = 'tab'
|
15
|
+
end
|
16
|
+
@allow_non_nested_indentation = config['allow_non_nested_indentation']
|
9
17
|
@indent = 0
|
18
|
+
@indentations = {}
|
10
19
|
yield
|
11
20
|
end
|
12
21
|
|
@@ -28,31 +37,23 @@ module SCSSLint
|
|
28
37
|
# sibling or its parent, as indentation isn't possible
|
29
38
|
return if nodes_on_same_line?(previous_node(node), node)
|
30
39
|
|
31
|
-
|
32
|
-
other_character = ' '
|
33
|
-
other_character_name = 'space'
|
34
|
-
else
|
35
|
-
other_character = "\t"
|
36
|
-
other_character_name = 'tab'
|
37
|
-
end
|
38
|
-
|
39
|
-
check_indent_width(node, other_character, @indent_character, other_character_name)
|
40
|
+
check_indent_width(node)
|
40
41
|
end
|
41
42
|
|
42
|
-
def check_indent_width(node
|
43
|
+
def check_indent_width(node)
|
43
44
|
actual_indent = node_indent(node)
|
44
45
|
|
45
|
-
if actual_indent.include?(other_character)
|
46
|
+
if actual_indent.include?(@other_character)
|
46
47
|
add_lint(node.line,
|
47
|
-
"Line should be indented with #{
|
48
|
-
"not #{other_character_name}s")
|
48
|
+
"Line should be indented with #{@indent_character}s, " \
|
49
|
+
"not #{@other_character_name}s")
|
49
50
|
return true
|
50
51
|
end
|
51
52
|
|
52
|
-
if
|
53
|
-
check_arbitrary_indent(node, actual_indent.length
|
53
|
+
if @allow_non_nested_indentation
|
54
|
+
check_arbitrary_indent(node, actual_indent.length)
|
54
55
|
else
|
55
|
-
check_regular_indent(node, actual_indent.length
|
56
|
+
check_regular_indent(node, actual_indent.length)
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
@@ -61,7 +62,7 @@ module SCSSLint
|
|
61
62
|
def visit_if(node)
|
62
63
|
check_indentation(node)
|
63
64
|
|
64
|
-
if
|
65
|
+
if @allow_non_nested_indentation
|
65
66
|
yield # Continue linting else statement
|
66
67
|
else
|
67
68
|
visit(node.else) if node.else
|
@@ -139,51 +140,39 @@ module SCSSLint
|
|
139
140
|
same_position?(node.source_range.end_pos, first_child_source.start_pos)
|
140
141
|
end
|
141
142
|
|
142
|
-
def check_regular_indent(node, actual_indent
|
143
|
+
def check_regular_indent(node, actual_indent)
|
143
144
|
return if actual_indent == @indent
|
144
145
|
|
145
|
-
add_lint(node.line,
|
146
|
-
"Line should be indented #{@indent} #{character_name}s, " \
|
147
|
-
"but was indented #{actual_indent} #{character_name}s")
|
146
|
+
add_lint(node.line, lint_message(@indent, actual_indent))
|
148
147
|
true
|
149
148
|
end
|
150
149
|
|
151
|
-
def check_arbitrary_indent(node, actual_indent
|
152
|
-
|
153
|
-
# long as it's a multiple of the indent width
|
154
|
-
if ruleset_under_root_node?(node)
|
155
|
-
unless actual_indent % @indent_width == 0
|
156
|
-
add_lint(node.line,
|
157
|
-
"Line must be indented a multiple of #{@indent_width} " \
|
158
|
-
"#{character_name}s, but was indented #{actual_indent} #{character_name}s")
|
159
|
-
return true
|
160
|
-
end
|
161
|
-
end
|
150
|
+
def check_arbitrary_indent(node, actual_indent)
|
151
|
+
return if check_root_ruleset_indent(node, actual_indent)
|
162
152
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
153
|
+
# Allow any root-level node (i.e. one that would normally have an indent
|
154
|
+
# of zero) to have an arbitrary amount of indent
|
155
|
+
return if @indent == 0
|
156
|
+
|
157
|
+
return if one_shift_greater_than_parent?(node, actual_indent)
|
158
|
+
parent_indent = node_indent(node_indent_parent(node)).length
|
159
|
+
expected_indent = parent_indent + @indent_width
|
160
|
+
add_lint(node.line, lint_message(expected_indent, actual_indent))
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
164
|
+
# Allow rulesets to be indented any amount when the indent is zero, as long
|
165
|
+
# as it's a multiple of the indent width
|
166
|
+
def check_root_ruleset_indent(node, actual_indent)
|
167
|
+
# Whether node is a ruleset not nested within any other ruleset.
|
168
|
+
if @indent == 0 && node.is_a?(Sass::Tree::RuleNode)
|
169
|
+
unless actual_indent % @indent_width == 0
|
170
|
+
add_lint(node.line, lint_message("a multiple of #{@indent_width}", actual_indent))
|
168
171
|
return true
|
169
172
|
end
|
170
|
-
elsif !one_shift_greater_than_parent?(node, actual_indent)
|
171
|
-
parent_indent = node_indent(node_indent_parent(node)).length
|
172
|
-
expected_indent = parent_indent + @indent_width
|
173
|
-
|
174
|
-
add_lint(node.line,
|
175
|
-
"Line should be indented #{expected_indent} #{character_name}s, " \
|
176
|
-
"but was indented #{actual_indent} #{character_name}s")
|
177
|
-
return true
|
178
173
|
end
|
179
|
-
end
|
180
174
|
|
181
|
-
|
182
|
-
#
|
183
|
-
# @param node [Sass::Tree::Node]
|
184
|
-
# @return [true,false]
|
185
|
-
def ruleset_under_root_node?(node)
|
186
|
-
@indent == 0 && node.is_a?(Sass::Tree::RuleNode)
|
175
|
+
false
|
187
176
|
end
|
188
177
|
|
189
178
|
# Returns whether node is indented exactly one indent width greater than its
|
@@ -202,7 +191,7 @@ module SCSSLint
|
|
202
191
|
# @param node [Sass::Tree::Node]
|
203
192
|
# @return [Integer]
|
204
193
|
def node_indent(node)
|
205
|
-
engine.lines[node.line - 1][/^(\s*)
|
194
|
+
@indentations[node] ||= engine.lines[node.line - 1][/^(\s*)/]
|
206
195
|
end
|
207
196
|
|
208
197
|
def node_indent_parent(node)
|
@@ -215,5 +204,10 @@ module SCSSLint
|
|
215
204
|
|
216
205
|
node.node_parent
|
217
206
|
end
|
207
|
+
|
208
|
+
def lint_message(expected, actual)
|
209
|
+
"Line should be indented #{expected} #{@indent_character}s, but was " \
|
210
|
+
"indented #{actual} #{@indent_character}s"
|
211
|
+
end
|
218
212
|
end
|
219
213
|
end
|