forked from sds/scss-lint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathname_format.rb
128 lines (106 loc) · 3.95 KB
/
name_format.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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
module SCSSLint
# Checks the format of declared names of functions, mixins, and variables.
class Linter::NameFormat < Linter
include LinterRegistry
CSS_FUNCTION_WHITELIST = %w[
rotateX rotateY rotateZ
scaleX scaleY scaleZ
skewX skewY
translateX translateY translateZ
linear-gradient repeating-linear-gradient
radial-gradient repeating-radial-gradient
cubic-bezier
fit-content
].to_set.freeze
SCSS_FUNCTION_WHITELIST = %w[
adjust-hue adjust-color scale-color change-color ie-hex-str
str-length str-insert str-index str-slice to-upper-case to-lower-case
list-separator
map-get map-merge map-remove map-keys map-values map-has-key
selector-nest selector-append selector-extend selector-replace
selector-unify is-superselector simple-selectors selector-parse
feature-exists variable-exists global-variable-exists function-exists
mixin-exists type-of
unique-id
].to_set.freeze
def visit_function(node)
check_name(node, 'function')
yield # Continue into content block of this function definition
end
def visit_mixin(node)
check_name(node, 'mixin') unless whitelist?(node.name)
yield # Continue into content block of this mixin's block
end
def visit_mixindef(node)
check_name(node, 'mixin')
yield # Continue into content block of this mixin definition
end
def visit_script_funcall(node)
check_name(node, 'function') unless whitelist?(node.name)
yield # Continue linting any arguments of this function call
end
def visit_script_variable(node)
check_name(node, 'variable')
end
def visit_variable(node)
check_name(node, 'variable')
yield # Continue into expression tree for this variable definition
end
private
def whitelist?(name)
CSS_FUNCTION_WHITELIST.include?(name) ||
SCSS_FUNCTION_WHITELIST.include?(name)
end
def check_name(node, node_type, node_text = node.name)
node_text = trim_underscore_prefix(node_text)
return unless violation = violated_convention(node_text, node_type)
add_lint(node,
"Name of #{node_type} `#{node_text}` #{violation[:explanation]}")
end
# Removes underscore prefix from name if leading underscores are allowed.
def trim_underscore_prefix(name)
if config['allow_leading_underscore']
# Remove if there is a single leading underscore
name = name.sub(/^_(?!_)/, '')
end
name
end
CONVENTIONS = {
'camel_case' => {
explanation: 'should be written in camelCase format',
validator: ->(name) { name =~ /^[a-z][a-zA-Z0-9]*$/ },
},
'snake_case' => {
explanation: 'should be written in snake_case',
validator: ->(name) { name !~ /[^_a-z0-9]/ },
},
'hyphenated_lowercase' => {
explanation: 'should be written in all lowercase letters with hyphens ' \
'instead of underscores',
validator: ->(name) { name !~ /[_A-Z]/ },
},
}.freeze
def violated_convention(name_string, type)
convention_name = convention_name(type)
existing_convention = CONVENTIONS[convention_name]
convention = (existing_convention || {
validator: ->(name) { name =~ /#{convention_name}/ }
}).merge(
explanation: convention_explanation(type), # Allow explanation to be customized
)
convention unless convention[:validator].call(name_string)
end
def convention_name(type)
config["#{type}_convention"] ||
config['convention'] ||
'hyphenated_lowercase'
end
def convention_explanation(type)
existing_convention = CONVENTIONS[convention_name(type)]
config["#{type}_convention_explanation"] ||
config['convention_explanation'] ||
(existing_convention && existing_convention[:explanation]) ||
"should match regex /#{convention_name(type)}/"
end
end
end