scss_beautifier 0.1.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 +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
|