-
Notifications
You must be signed in to change notification settings - Fork 465
/
length_variable.rb
107 lines (94 loc) · 3.29 KB
/
length_variable.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
module SCSSLint
# Ensures length literals are used only in variable declarations.
class Linter::LengthVariable < Linter
include LinterRegistry
LENGTH_UNITS = [
'ch', 'em', 'ex', 'rem', # Font-relative lengths
'cm', 'in', 'mm', 'pc', 'pt', 'px', 'q', # Absolute lengths
'vh', 'vw', 'vmin', 'vmax' # Viewport-percentage lengths
].freeze
LENGTH_RE = %r{
(?:^|[\s+\-/*()]) # math or space separated, or beginning of string
( # capture whole length
0 # unitless zero
|
[-+]? # optional sign
(?:
\.\d+ # with leading decimal, e.g. .5
|
\d+(\.\d+)? # whole or maybe with trailing decimal
)
(?:#{LENGTH_UNITS.join('|')}) # unit!
)
(?:$|[\s+\-/*()]) # math or space separated, or end of string
}x.freeze
def visit_prop(node)
return if allowed_prop?(node)
lint_lengths(node)
end
def visit_mixindef(node)
lint_lengths(node)
end
def visit_media(node)
lint_lengths(node)
end
def visit_mixin(node)
lint_lengths(node)
end
private
def lint_lengths(node)
lengths = extract_lengths(node)
lengths = [lengths].flatten.compact.uniq
lengths -= config['allowed_lengths'] if config['allowed_lengths']
lengths.each do |length|
record_lint(node, length) unless lengths.empty?
end
end
def record_lint(node, length)
add_lint node, "Length literals like `#{length}` should only be used in " \
'variable declarations; they should be referred to via ' \
'variables everywhere else.'
end
def allowed_prop?(node)
config['allowed_properties'] && config['allowed_properties'].include?(node.name.first.to_s)
end
# Though long, This method is clear enough in a boring, dispatch kind of way.
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
def extract_lengths(node)
case node
when Sass::Tree::PropNode
extract_lengths(node.value)
when Sass::Script::Tree::Literal
extract_lengths_from_string(node.value)
when String
extract_lengths_from_string(node)
when Sass::Script::Tree::Funcall,
Sass::Tree::MixinNode,
Sass::Tree::MixinDefNode
extract_lengths_from_list(*node.args)
when Sass::Script::Tree::ListLiteral
extract_lengths_from_list(*node.elements)
when Sass::Tree::MediaNode
extract_lengths_from_list(*node.query)
when Array
extract_lengths_from_list(*node)
when Sass::Script::Tree::Interpolation
extract_lengths_from_list(node.before, node.mid, node.after)
when Sass::Script::Tree::Operation
extract_lengths_from_list(node.operand1, node.operand2)
when Sass::Script::Tree::UnaryOperation
extract_lengths(node.operand)
when Sass::Script::Tree::Variable
nil
end
end
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
def extract_lengths_from_string(string)
matchdata = string.to_s.match(LENGTH_RE)
matchdata && matchdata.captures
end
def extract_lengths_from_list(*values)
values.map { |v| extract_lengths(v) }
end
end
end