scss_beautifier 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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