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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -0
  4. data/CODE_OF_CONDUCT.md +49 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +32 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +31 -0
  9. data/Rakefile +10 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/data/default_config.yml +49 -0
  13. data/data/pseudo_elements.txt +97 -0
  14. data/exe/scss-beautify +6 -0
  15. data/lib/scss_beautifier.rb +35 -0
  16. data/lib/scss_beautifier/cli.rb +20 -0
  17. data/lib/scss_beautifier/config.rb +21 -0
  18. data/lib/scss_beautifier/convert.rb +31 -0
  19. data/lib/scss_beautifier/formatters/bang_format.rb +21 -0
  20. data/lib/scss_beautifier/formatters/border_zero.rb +22 -0
  21. data/lib/scss_beautifier/formatters/color.rb +24 -0
  22. data/lib/scss_beautifier/formatters/comment.rb +17 -0
  23. data/lib/scss_beautifier/formatters/debug.rb +8 -0
  24. data/lib/scss_beautifier/formatters/declaration_order.rb +23 -0
  25. data/lib/scss_beautifier/formatters/empty_rule.rb +19 -0
  26. data/lib/scss_beautifier/formatters/leading_zero.rb +12 -0
  27. data/lib/scss_beautifier/formatters/name_format.rb +45 -0
  28. data/lib/scss_beautifier/formatters/property_sort_order.rb +23 -0
  29. data/lib/scss_beautifier/formatters/pseudo_element.rb +20 -0
  30. data/lib/scss_beautifier/formatters/qualifying_element.rb +14 -0
  31. data/lib/scss_beautifier/formatters/selector.rb +8 -0
  32. data/lib/scss_beautifier/formatters/shorthand.rb +112 -0
  33. data/lib/scss_beautifier/formatters/string_quotes.rb +33 -0
  34. data/lib/scss_beautifier/formatters/trailing_zero.rb +24 -0
  35. data/lib/scss_beautifier/options.rb +94 -0
  36. data/lib/scss_beautifier/version.rb +3 -0
  37. data/scss_beautifier.gemspec +28 -0
  38. data/tmp/bang.scss +3 -0
  39. data/tmp/border0.scss +3 -0
  40. data/tmp/colorkeyword.scss +3 -0
  41. data/tmp/colorshort.scss +3 -0
  42. data/tmp/comments.scss +5 -0
  43. data/tmp/debug.scss +4 -0
  44. data/tmp/declarationorder.scss +31 -0
  45. data/tmp/dump.mdown +63 -0
  46. data/tmp/elseplacement.scss +18 -0
  47. data/tmp/empty.scss +3 -0
  48. data/tmp/leadingzero.scss +17 -0
  49. data/tmp/nameformat.scss +30 -0
  50. data/tmp/pm.scss +228 -0
  51. data/tmp/pm2.scss +235 -0
  52. data/tmp/propertysortorder.scss +19 -0
  53. data/tmp/pseudoelement.scss +13 -0
  54. data/tmp/qualifyingelement.scss +23 -0
  55. data/tmp/selectors.scss +3 -0
  56. data/tmp/shorthand.scss +15 -0
  57. data/tmp/string_quotes.scss +12 -0
  58. data/tmp/test.scss +1 -0
  59. data/tmp/test2.scss +35 -0
  60. data/tmp/trailing_zero.scss +15 -0
  61. 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,8 @@
1
+ class SCSSBeautifier::Formatters::Debug < Sass::Tree::Visitors::Base
2
+ def visit_rule(node)
3
+ filtered = node.children.reject do |c|
4
+ Sass::Tree::DebugNode === c
5
+ end
6
+ node.children = filtered
7
+ end
8
+ 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,8 @@
1
+ class SCSSBeautifier::Formatters::Selector < Sass::Tree::Visitors::Base
2
+ def visit_rule(node)
3
+ node.rule = node.rule.each do |rule|
4
+ rule.gsub!(/,/, ",\n")
5
+ end
6
+ node.send(:try_to_parse_non_interpolated_rules)
7
+ end
8
+ 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