scss_lint 0.42.2 → 0.43.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.
- 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
|