scss_beautifier 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +21 -0
- data/README.md +31 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/data/default_config.yml +49 -0
- data/data/pseudo_elements.txt +97 -0
- data/exe/scss-beautify +6 -0
- data/lib/scss_beautifier.rb +35 -0
- data/lib/scss_beautifier/cli.rb +20 -0
- data/lib/scss_beautifier/config.rb +21 -0
- data/lib/scss_beautifier/convert.rb +31 -0
- data/lib/scss_beautifier/formatters/bang_format.rb +21 -0
- data/lib/scss_beautifier/formatters/border_zero.rb +22 -0
- data/lib/scss_beautifier/formatters/color.rb +24 -0
- data/lib/scss_beautifier/formatters/comment.rb +17 -0
- data/lib/scss_beautifier/formatters/debug.rb +8 -0
- data/lib/scss_beautifier/formatters/declaration_order.rb +23 -0
- data/lib/scss_beautifier/formatters/empty_rule.rb +19 -0
- data/lib/scss_beautifier/formatters/leading_zero.rb +12 -0
- data/lib/scss_beautifier/formatters/name_format.rb +45 -0
- data/lib/scss_beautifier/formatters/property_sort_order.rb +23 -0
- data/lib/scss_beautifier/formatters/pseudo_element.rb +20 -0
- data/lib/scss_beautifier/formatters/qualifying_element.rb +14 -0
- data/lib/scss_beautifier/formatters/selector.rb +8 -0
- data/lib/scss_beautifier/formatters/shorthand.rb +112 -0
- data/lib/scss_beautifier/formatters/string_quotes.rb +33 -0
- data/lib/scss_beautifier/formatters/trailing_zero.rb +24 -0
- data/lib/scss_beautifier/options.rb +94 -0
- data/lib/scss_beautifier/version.rb +3 -0
- data/scss_beautifier.gemspec +28 -0
- data/tmp/bang.scss +3 -0
- data/tmp/border0.scss +3 -0
- data/tmp/colorkeyword.scss +3 -0
- data/tmp/colorshort.scss +3 -0
- data/tmp/comments.scss +5 -0
- data/tmp/debug.scss +4 -0
- data/tmp/declarationorder.scss +31 -0
- data/tmp/dump.mdown +63 -0
- data/tmp/elseplacement.scss +18 -0
- data/tmp/empty.scss +3 -0
- data/tmp/leadingzero.scss +17 -0
- data/tmp/nameformat.scss +30 -0
- data/tmp/pm.scss +228 -0
- data/tmp/pm2.scss +235 -0
- data/tmp/propertysortorder.scss +19 -0
- data/tmp/pseudoelement.scss +13 -0
- data/tmp/qualifyingelement.scss +23 -0
- data/tmp/selectors.scss +3 -0
- data/tmp/shorthand.scss +15 -0
- data/tmp/string_quotes.scss +12 -0
- data/tmp/test.scss +1 -0
- data/tmp/test2.scss +35 -0
- data/tmp/trailing_zero.scss +15 -0
- metadata +176 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::BangFormat < Sass::Tree::Visitors::Base
|
2
|
+
# def visit_extend(node)
|
3
|
+
# format_bang(node)
|
4
|
+
# end
|
5
|
+
|
6
|
+
def visit_prop(node)
|
7
|
+
node.value.each{ |item| format_bang(item) }
|
8
|
+
end
|
9
|
+
|
10
|
+
# def visit_variable(node)
|
11
|
+
# format_bang(node)
|
12
|
+
# end
|
13
|
+
|
14
|
+
def format_bang(item)
|
15
|
+
if Sass::Script::Tree::Literal === item && Sass::Script::Value::String === item.value
|
16
|
+
if item.value.value.include?("!")
|
17
|
+
item.instance_variable_set(:@value, Sass::Script::Value::String.new(item.value.value.gsub(/\s*!\s*/, ' !')))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::BorderZero < Sass::Tree::Visitors::Base
|
2
|
+
BORDER_PROPERTIES = %w[
|
3
|
+
border
|
4
|
+
border-top
|
5
|
+
border-right
|
6
|
+
border-bottom
|
7
|
+
border-left
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
|
11
|
+
def visit_prop(node)
|
12
|
+
return unless BORDER_PROPERTIES.include?(node.name.first.to_s)
|
13
|
+
# return unless node.value.length == 1
|
14
|
+
format_border(node.value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def format_border(node)
|
18
|
+
return unless Sass::Script::Tree::Literal === node
|
19
|
+
return unless node.value.value == "0"
|
20
|
+
node.instance_variable_set(:@value, Sass::Script::Value::String.new("none"))
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::Color < Sass::Tree::Visitors::Base
|
2
|
+
def visit_prop(node)
|
3
|
+
if node.value.respond_to?(:each)
|
4
|
+
node.value.each{ |item| format_color(item) }
|
5
|
+
else
|
6
|
+
format_color(node.value)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def format_color(item)
|
11
|
+
if Sass::Script::Tree::Literal === item && Sass::Script::Value::String === item.value
|
12
|
+
if color = Sass::Script::Value::Color::COLOR_NAMES[item.value.value]
|
13
|
+
color_value = Sass::Script::Value::Color.new(color)
|
14
|
+
color_value.options = {}
|
15
|
+
item.instance_variable_set(:@value, Sass::Script::Value::String.new(color_value.inspect))
|
16
|
+
elsif item.value.value =~ /(#\h{3})(?!\h)/
|
17
|
+
hex = item.value.value
|
18
|
+
long_form = [hex[0..1], hex[1], hex[2], hex[2], hex[3], hex[3]].join
|
19
|
+
item.instance_variable_set(:@value, Sass::Script::Value::String.new(long_form))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::Comment < Sass::Tree::Visitors::Base
|
2
|
+
def visit_comment(node)
|
3
|
+
# require 'pry'; binding.pry
|
4
|
+
format_comment(node)# if !node.invisible?
|
5
|
+
end
|
6
|
+
|
7
|
+
def format_comment(node)
|
8
|
+
node.value.first.gsub!(/\/\*\s?/, "// ")
|
9
|
+
node.value.last.gsub!(/\*\//, "")
|
10
|
+
# require 'pry'; binding.pry
|
11
|
+
node.value.each do |item|
|
12
|
+
if String === item
|
13
|
+
item.gsub!(/\n\s?/, "\n// ")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::DeclarationOrder < Sass::Tree::Visitors::Base
|
2
|
+
# TODO: Account for if/else blocks and include blocks
|
3
|
+
NODE_ORDER = ["ExtendNode", "MixinNode", "PropNode", "RuleNode"]
|
4
|
+
|
5
|
+
def visit_rule(node)
|
6
|
+
order_children(node)
|
7
|
+
visit_children(node)
|
8
|
+
end
|
9
|
+
|
10
|
+
def order_children(node)
|
11
|
+
node_hash = Hash.new { |h, k| h[k] = [] }
|
12
|
+
node.children.each do |child|
|
13
|
+
hash_key = child.class.to_s.split("::").last
|
14
|
+
node_hash[hash_key] << child
|
15
|
+
end
|
16
|
+
|
17
|
+
compiled_array = NODE_ORDER.reduce([]) do |memo, key|
|
18
|
+
memo.concat(node_hash[key])
|
19
|
+
end
|
20
|
+
|
21
|
+
node.children = compiled_array
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::EmptyRule < Sass::Tree::Visitors::Base
|
2
|
+
def visit_root(node)
|
3
|
+
remove_empty_rule(node)
|
4
|
+
yield
|
5
|
+
remove_empty_rule(node)
|
6
|
+
end
|
7
|
+
def visit_rule(node)
|
8
|
+
remove_empty_rule(node)
|
9
|
+
visit_children(node)
|
10
|
+
remove_empty_rule(node)
|
11
|
+
end
|
12
|
+
|
13
|
+
def remove_empty_rule(node)
|
14
|
+
filtered = node.children.reject do |c|
|
15
|
+
Sass::Tree::RuleNode === c && c.children.empty?
|
16
|
+
end
|
17
|
+
node.children = filtered
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::LeadingZero < Sass::Tree::Visitors::Base
|
2
|
+
# ExcludeZero Only
|
3
|
+
def visit_prop(node)
|
4
|
+
if Sass::Script::Tree::Literal === node.value && Sass::Script::Value::String === node.value.value
|
5
|
+
node_value = node.value.value
|
6
|
+
|
7
|
+
if node.value.value.to_s.match(/\b0./)
|
8
|
+
node.instance_variable_set(:@value, Sass::Script::Value::String.new(node.value.value.to_s.gsub(/\b0/, '')))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::NameFormat < Sass::Tree::Visitors::Base
|
2
|
+
def visit_function(node)
|
3
|
+
check_name(node)
|
4
|
+
end
|
5
|
+
|
6
|
+
def visit_mixin(node)
|
7
|
+
check_name(node)
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit_placeholder(node)
|
11
|
+
check_name(node)
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_mixindef(node)
|
15
|
+
check_name(node)
|
16
|
+
end
|
17
|
+
|
18
|
+
def visit_script_funcall(node)
|
19
|
+
check_name(node)
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_script_variable(node)
|
23
|
+
check_name(node)
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit_variable(node)
|
27
|
+
check_name(node)
|
28
|
+
end
|
29
|
+
|
30
|
+
def visit_rule(node)
|
31
|
+
check_rule(node)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def check_name(node)
|
37
|
+
# return unless node.name
|
38
|
+
node.instance_variable_set(:@name, Sass::Script::Value::String.new(node.name.to_s.gsub(/[[:upper:]]/) { "-#{$&}" }.downcase.gsub(/_/, '-')))
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_rule(node)
|
42
|
+
node.rule = Sass::Util.strip_string_array(node.rule.map { |r| r.to_s.gsub(/[[:upper:]]/) { "-#{$&}" }.downcase.gsub(/_/, '-') })
|
43
|
+
node.send(:try_to_parse_non_interpolated_rules)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::PropertySortOrder < Sass::Tree::Visitors::Base
|
2
|
+
def visit_rule(node)
|
3
|
+
order_children(node)
|
4
|
+
visit_children(node)
|
5
|
+
end
|
6
|
+
|
7
|
+
def order_children(node)
|
8
|
+
prop_node_indices = []
|
9
|
+
prop_nodes = []
|
10
|
+
node.children.each_with_index do |child, index|
|
11
|
+
hash_key = child.class.to_s.split("::").last
|
12
|
+
if hash_key == 'PropNode'
|
13
|
+
prop_node_indices << index
|
14
|
+
prop_nodes << child
|
15
|
+
end
|
16
|
+
end
|
17
|
+
prop_nodes.sort! { |x,y| x.name[0] <=> y.name[0] }
|
18
|
+
# Replace children being respective of other types of props/funcs/etc
|
19
|
+
prop_nodes.each_with_index do |n, index|
|
20
|
+
node.children[prop_node_indices[index]] = n
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
PSEUDO_ELEMENTS = File.read('./data/pseudo_elements.txt').split("\n")
|
2
|
+
|
3
|
+
class SCSSBeautifier::Formatters::PseudoElement < Sass::Tree::Visitors::Base
|
4
|
+
def visit_rule(node)
|
5
|
+
check_pseudo(node) if node.rule.join.match(/::?/)
|
6
|
+
visit_children(node)
|
7
|
+
end
|
8
|
+
|
9
|
+
def check_pseudo(node)
|
10
|
+
node.rule = Sass::Util.strip_string_array(node.rule.map { |r|
|
11
|
+
require_double_colon = PSEUDO_ELEMENTS.index(r.split(":").last)
|
12
|
+
|
13
|
+
colon_type = require_double_colon ? '::' : ':'
|
14
|
+
|
15
|
+
r.gsub(/::?/, colon_type)
|
16
|
+
})
|
17
|
+
|
18
|
+
node.send(:try_to_parse_non_interpolated_rules)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::QualifyingElement < Sass::Tree::Visitors::Base
|
2
|
+
def visit_rule(node)
|
3
|
+
check_qualifying_element(node)
|
4
|
+
visit_children(node)
|
5
|
+
end
|
6
|
+
|
7
|
+
def check_qualifying_element(node)
|
8
|
+
node.rule.each do |r|
|
9
|
+
# What do we do when something breaks this rule?
|
10
|
+
end
|
11
|
+
|
12
|
+
node.send(:try_to_parse_non_interpolated_rules)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::Shorthand < Sass::Tree::Visitors::Base
|
2
|
+
def visit_prop(node)
|
3
|
+
property_name = node.name.join
|
4
|
+
return unless SHORTHANDABLE_PROPERTIES.include?(property_name)
|
5
|
+
|
6
|
+
case node.value
|
7
|
+
when Sass::Script::Tree::Literal
|
8
|
+
update_script_literal(property_name, node)
|
9
|
+
when Sass::Script::Tree::ListLiteral
|
10
|
+
update_script_list(property_name, node)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def update_script_literal(pName, node)
|
15
|
+
nValue = node.value
|
16
|
+
|
17
|
+
return unless values = nValue.value.to_s.strip[LIST_LITERAL_REGEX, 1]
|
18
|
+
values = values.split(' ')
|
19
|
+
|
20
|
+
check_shorthand(pName, node, values)
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_script_list(pName, node)
|
24
|
+
nValue = node.value
|
25
|
+
|
26
|
+
check_shorthand(pName, node, nValue.children.map(&:to_sass), true)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
SHORTHANDABLE_PROPERTIES = %w[
|
32
|
+
border-color
|
33
|
+
border-radius
|
34
|
+
border-style
|
35
|
+
border-width
|
36
|
+
margin
|
37
|
+
padding
|
38
|
+
].freeze
|
39
|
+
|
40
|
+
LIST_LITERAL_REGEX = /
|
41
|
+
\A
|
42
|
+
(\S+\s+\S+(\s+\S+){0,2}) # Two to four values separated by spaces
|
43
|
+
(\s+!\w+)? # Ignore `!important` priority overrides
|
44
|
+
\z
|
45
|
+
/x
|
46
|
+
|
47
|
+
def check_shorthand(prop, node, values)
|
48
|
+
shortest_form = condensed_shorthand(*values)
|
49
|
+
|
50
|
+
return if values == shortest_form
|
51
|
+
|
52
|
+
node.instance_variable_set(:@value, Sass::Script::Value::String.new(shortest_form.join(' ')))
|
53
|
+
end
|
54
|
+
|
55
|
+
def allowed?(size)
|
56
|
+
# return false unless config['allowed_shorthands']
|
57
|
+
# config['allowed_shorthands'].map(&:to_i).include?(size)
|
58
|
+
[1, 2, 3, 4].map(&:to_i).include?(size)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param top [String]
|
62
|
+
# @param right [String]
|
63
|
+
# @param bottom [String]
|
64
|
+
# @param left [String]
|
65
|
+
# @return [Array]
|
66
|
+
def condensed_shorthand(top, right, bottom = nil, left = nil)
|
67
|
+
if condense_to_one_value?(top, right, bottom, left)
|
68
|
+
[top]
|
69
|
+
elsif condense_to_two_values?(top, right, bottom, left)
|
70
|
+
[top, right]
|
71
|
+
elsif condense_to_three_values?(top, right, bottom, left)
|
72
|
+
[top, right, bottom]
|
73
|
+
else
|
74
|
+
[top, right, bottom, left].compact
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param top [String]
|
79
|
+
# @param right [String]
|
80
|
+
# @param bottom [String]
|
81
|
+
# @param left [String]
|
82
|
+
# @return [Boolean]
|
83
|
+
def condense_to_one_value?(top, right, bottom, left)
|
84
|
+
return unless allowed?(1)
|
85
|
+
return unless top == right
|
86
|
+
|
87
|
+
top == bottom && (bottom == left || left.nil?) ||
|
88
|
+
bottom.nil? && left.nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
# @param top [String]
|
92
|
+
# @param right [String]
|
93
|
+
# @param bottom [String]
|
94
|
+
# @param left [String]
|
95
|
+
# @return [Boolean]
|
96
|
+
def condense_to_two_values?(top, right, bottom, left)
|
97
|
+
return unless allowed?(2)
|
98
|
+
|
99
|
+
top == bottom && right == left ||
|
100
|
+
top == bottom && left.nil? && top != right
|
101
|
+
end
|
102
|
+
|
103
|
+
# @param right [String]
|
104
|
+
# @param left [String]
|
105
|
+
# @return [Boolean]
|
106
|
+
def condense_to_three_values?(_, right, __, left)
|
107
|
+
return unless allowed?(3)
|
108
|
+
|
109
|
+
right == left
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::StringQuotes < Sass::Tree::Visitors::Base
|
2
|
+
def visit_prop(node)
|
3
|
+
check_quotes(node)
|
4
|
+
visit_children(node)
|
5
|
+
end
|
6
|
+
def check_quotes(node)
|
7
|
+
return unless node.value.is_a?(Sass::Script::Tree::Literal)
|
8
|
+
|
9
|
+
# TODO Check for single or double quote preference
|
10
|
+
str_without_quotes = extract_string_without_quotes(node.value.value.to_s)
|
11
|
+
change_to_single_quotes(node) if str_without_quotes && !str_without_quotes.match(/'/)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
STRING_WITHOUT_QUOTES_REGEX = %r{
|
17
|
+
\A
|
18
|
+
["'](.*)["'] # Extract text between quotes
|
19
|
+
\s*\)?\s*;?\s* # Sometimes the Sass parser includes a trailing ) or ;
|
20
|
+
(//.*)? # Exclude any trailing comments that might have snuck in
|
21
|
+
\z
|
22
|
+
}x
|
23
|
+
|
24
|
+
def change_to_single_quotes(node)
|
25
|
+
new_val = node.value.value.to_s.gsub('"', '\'')
|
26
|
+
node.instance_variable_set(:@value, Sass::Script::Value::String.new(new_val))
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_string_without_quotes(source)
|
30
|
+
return unless match = STRING_WITHOUT_QUOTES_REGEX.match(source)
|
31
|
+
match[1]
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class SCSSBeautifier::Formatters::TrailingZero < Sass::Tree::Visitors::Base
|
2
|
+
def visit_prop(node)
|
3
|
+
check_trailing_zero(node)
|
4
|
+
visit_children(node)
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
FRACTIONAL_DIGIT_REGEX = /^-?(\d*\.\d+[a-zA-Z]*)/
|
10
|
+
|
11
|
+
def check_trailing_zero(node)
|
12
|
+
return unless number = node.value.to_s[FRACTIONAL_DIGIT_REGEX, 1]
|
13
|
+
|
14
|
+
return unless match = /^(\d*\.(?:[0-9]*[1-9]|[1-9])*)0+([a-zA-Z]*)$/.match(number)
|
15
|
+
|
16
|
+
fixed_number = match[1]
|
17
|
+
extension = match[2]
|
18
|
+
|
19
|
+
# Handle special case of 0 being the only trailing digit
|
20
|
+
fixed_number = fixed_number[0..-2] if fixed_number.end_with?('.')
|
21
|
+
fixed_number = 0 if fixed_number.empty? # Handle ".0" -> "0"
|
22
|
+
node.instance_variable_set(:@value, Sass::Script::Value::String.new(fixed_number + extension))
|
23
|
+
end
|
24
|
+
end
|