oreorenasass 3.4.4 → 3.4.5

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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +50 -70
  4. data/Rakefile +5 -26
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/sass +1 -1
  8. data/bin/scss +1 -1
  9. data/lib/sass.rb +12 -19
  10. data/lib/sass/cache_stores/base.rb +2 -2
  11. data/lib/sass/cache_stores/chain.rb +1 -2
  12. data/lib/sass/cache_stores/filesystem.rb +5 -1
  13. data/lib/sass/cache_stores/memory.rb +1 -1
  14. data/lib/sass/cache_stores/null.rb +2 -2
  15. data/lib/sass/callbacks.rb +0 -1
  16. data/lib/sass/css.rb +13 -11
  17. data/lib/sass/engine.rb +173 -424
  18. data/lib/sass/environment.rb +58 -148
  19. data/lib/sass/error.rb +14 -11
  20. data/lib/sass/exec.rb +703 -5
  21. data/lib/sass/importers/base.rb +6 -49
  22. data/lib/sass/importers/filesystem.rb +19 -44
  23. data/lib/sass/logger.rb +4 -1
  24. data/lib/sass/logger/base.rb +4 -2
  25. data/lib/sass/logger/log_level.rb +7 -3
  26. data/lib/sass/media.rb +23 -20
  27. data/lib/sass/plugin.rb +7 -7
  28. data/lib/sass/plugin/compiler.rb +145 -304
  29. data/lib/sass/plugin/configuration.rb +23 -18
  30. data/lib/sass/plugin/merb.rb +1 -1
  31. data/lib/sass/plugin/staleness_checker.rb +3 -3
  32. data/lib/sass/repl.rb +3 -3
  33. data/lib/sass/script.rb +8 -35
  34. data/lib/sass/script/{value/arg_list.rb → arg_list.rb} +25 -9
  35. data/lib/sass/script/bool.rb +18 -0
  36. data/lib/sass/script/color.rb +606 -0
  37. data/lib/sass/script/css_lexer.rb +4 -8
  38. data/lib/sass/script/css_parser.rb +2 -5
  39. data/lib/sass/script/funcall.rb +245 -0
  40. data/lib/sass/script/functions.rb +408 -1491
  41. data/lib/sass/script/interpolation.rb +79 -0
  42. data/lib/sass/script/lexer.rb +68 -172
  43. data/lib/sass/script/list.rb +85 -0
  44. data/lib/sass/script/literal.rb +221 -0
  45. data/lib/sass/script/{tree/node.rb → node.rb} +12 -22
  46. data/lib/sass/script/{value/null.rb → null.rb} +7 -14
  47. data/lib/sass/script/{value/number.rb → number.rb} +75 -152
  48. data/lib/sass/script/{tree/operation.rb → operation.rb} +24 -17
  49. data/lib/sass/script/parser.rb +110 -245
  50. data/lib/sass/script/string.rb +51 -0
  51. data/lib/sass/script/{tree/string_interpolation.rb → string_interpolation.rb} +4 -5
  52. data/lib/sass/script/{tree/unary_operation.rb → unary_operation.rb} +6 -6
  53. data/lib/sass/script/variable.rb +58 -0
  54. data/lib/sass/scss/css_parser.rb +3 -9
  55. data/lib/sass/scss/parser.rb +421 -450
  56. data/lib/sass/scss/rx.rb +11 -19
  57. data/lib/sass/scss/static_parser.rb +7 -321
  58. data/lib/sass/selector.rb +194 -68
  59. data/lib/sass/selector/abstract_sequence.rb +14 -29
  60. data/lib/sass/selector/comma_sequence.rb +25 -108
  61. data/lib/sass/selector/sequence.rb +66 -159
  62. data/lib/sass/selector/simple.rb +25 -23
  63. data/lib/sass/selector/simple_sequence.rb +63 -173
  64. data/lib/sass/shared.rb +1 -1
  65. data/lib/sass/supports.rb +15 -13
  66. data/lib/sass/tree/charset_node.rb +1 -1
  67. data/lib/sass/tree/comment_node.rb +3 -3
  68. data/lib/sass/tree/css_import_node.rb +11 -11
  69. data/lib/sass/tree/debug_node.rb +2 -2
  70. data/lib/sass/tree/directive_node.rb +4 -21
  71. data/lib/sass/tree/each_node.rb +8 -8
  72. data/lib/sass/tree/extend_node.rb +7 -14
  73. data/lib/sass/tree/for_node.rb +4 -4
  74. data/lib/sass/tree/function_node.rb +4 -9
  75. data/lib/sass/tree/if_node.rb +1 -1
  76. data/lib/sass/tree/import_node.rb +5 -4
  77. data/lib/sass/tree/media_node.rb +14 -4
  78. data/lib/sass/tree/mixin_def_node.rb +4 -4
  79. data/lib/sass/tree/mixin_node.rb +8 -21
  80. data/lib/sass/tree/node.rb +12 -54
  81. data/lib/sass/tree/prop_node.rb +20 -39
  82. data/lib/sass/tree/return_node.rb +2 -3
  83. data/lib/sass/tree/root_node.rb +3 -19
  84. data/lib/sass/tree/rule_node.rb +22 -35
  85. data/lib/sass/tree/supports_node.rb +13 -0
  86. data/lib/sass/tree/trace_node.rb +1 -2
  87. data/lib/sass/tree/variable_node.rb +3 -9
  88. data/lib/sass/tree/visitors/base.rb +8 -5
  89. data/lib/sass/tree/visitors/check_nesting.rb +19 -49
  90. data/lib/sass/tree/visitors/convert.rb +56 -74
  91. data/lib/sass/tree/visitors/cssize.rb +74 -202
  92. data/lib/sass/tree/visitors/deep_copy.rb +5 -10
  93. data/lib/sass/tree/visitors/extend.rb +7 -7
  94. data/lib/sass/tree/visitors/perform.rb +185 -278
  95. data/lib/sass/tree/visitors/set_options.rb +6 -20
  96. data/lib/sass/tree/visitors/to_css.rb +81 -234
  97. data/lib/sass/tree/warn_node.rb +2 -2
  98. data/lib/sass/tree/while_node.rb +2 -2
  99. data/lib/sass/util.rb +152 -522
  100. data/lib/sass/util/multibyte_string_scanner.rb +0 -2
  101. data/lib/sass/util/subset_map.rb +3 -4
  102. data/lib/sass/util/test.rb +1 -0
  103. data/lib/sass/version.rb +22 -20
  104. data/test/Gemfile +3 -0
  105. data/test/Gemfile.lock +10 -0
  106. data/test/sass/cache_test.rb +20 -62
  107. data/test/sass/callbacks_test.rb +1 -1
  108. data/test/sass/conversion_test.rb +2 -296
  109. data/test/sass/css2sass_test.rb +4 -23
  110. data/test/sass/engine_test.rb +354 -411
  111. data/test/sass/exec_test.rb +2 -2
  112. data/test/sass/extend_test.rb +145 -324
  113. data/test/sass/functions_test.rb +86 -873
  114. data/test/sass/importer_test.rb +21 -241
  115. data/test/sass/logger_test.rb +1 -1
  116. data/test/sass/more_results/more_import.css +1 -1
  117. data/test/sass/plugin_test.rb +26 -16
  118. data/test/sass/results/compact.css +1 -1
  119. data/test/sass/results/complex.css +4 -4
  120. data/test/sass/results/expanded.css +1 -1
  121. data/test/sass/results/import.css +1 -1
  122. data/test/sass/results/import_charset_ibm866.css +2 -2
  123. data/test/sass/results/mixins.css +17 -17
  124. data/test/sass/results/nested.css +1 -1
  125. data/test/sass/results/parent_ref.css +2 -2
  126. data/test/sass/results/script.css +3 -3
  127. data/test/sass/results/scss_import.css +1 -1
  128. data/test/sass/script_conversion_test.rb +7 -36
  129. data/test/sass/script_test.rb +53 -485
  130. data/test/sass/scss/css_test.rb +28 -143
  131. data/test/sass/scss/rx_test.rb +4 -4
  132. data/test/sass/scss/scss_test.rb +325 -2119
  133. data/test/sass/templates/scss_import.scss +1 -2
  134. data/test/sass/test_helper.rb +1 -1
  135. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  136. data/test/sass/util/subset_map_test.rb +2 -2
  137. data/test/sass/util_test.rb +1 -86
  138. data/test/test_helper.rb +8 -37
  139. metadata +19 -66
  140. data/lib/sass/exec/base.rb +0 -187
  141. data/lib/sass/exec/sass_convert.rb +0 -264
  142. data/lib/sass/exec/sass_scss.rb +0 -424
  143. data/lib/sass/features.rb +0 -47
  144. data/lib/sass/script/tree.rb +0 -16
  145. data/lib/sass/script/tree/funcall.rb +0 -306
  146. data/lib/sass/script/tree/interpolation.rb +0 -118
  147. data/lib/sass/script/tree/list_literal.rb +0 -77
  148. data/lib/sass/script/tree/literal.rb +0 -45
  149. data/lib/sass/script/tree/map_literal.rb +0 -64
  150. data/lib/sass/script/tree/selector.rb +0 -26
  151. data/lib/sass/script/tree/variable.rb +0 -57
  152. data/lib/sass/script/value.rb +0 -11
  153. data/lib/sass/script/value/base.rb +0 -240
  154. data/lib/sass/script/value/bool.rb +0 -35
  155. data/lib/sass/script/value/color.rb +0 -680
  156. data/lib/sass/script/value/helpers.rb +0 -262
  157. data/lib/sass/script/value/list.rb +0 -113
  158. data/lib/sass/script/value/map.rb +0 -70
  159. data/lib/sass/script/value/string.rb +0 -97
  160. data/lib/sass/selector/pseudo.rb +0 -256
  161. data/lib/sass/source/map.rb +0 -210
  162. data/lib/sass/source/position.rb +0 -39
  163. data/lib/sass/source/range.rb +0 -41
  164. data/lib/sass/stack.rb +0 -120
  165. data/lib/sass/tree/at_root_node.rb +0 -83
  166. data/lib/sass/tree/error_node.rb +0 -18
  167. data/lib/sass/tree/keyframe_rule_node.rb +0 -15
  168. data/lib/sass/util/cross_platform_random.rb +0 -19
  169. data/lib/sass/util/normalized_map.rb +0 -130
  170. data/lib/sass/util/ordered_hash.rb +0 -192
  171. data/test/sass/compiler_test.rb +0 -232
  172. data/test/sass/encoding_test.rb +0 -219
  173. data/test/sass/source_map_test.rb +0 -977
  174. data/test/sass/superselector_test.rb +0 -191
  175. data/test/sass/util/normalized_map_test.rb +0 -51
  176. data/test/sass/value_helpers_test.rb +0 -179
@@ -16,18 +16,13 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
16
16
  yield
17
17
  end
18
18
 
19
- def visit_error(node)
20
- node.expr = node.expr.deep_copy
21
- yield
22
- end
23
-
24
19
  def visit_each(node)
25
20
  node.list = node.list.deep_copy
26
21
  yield
27
22
  end
28
23
 
29
24
  def visit_extend(node)
30
- node.selector = node.selector.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
25
+ node.selector = node.selector.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
31
26
  yield
32
27
  end
33
28
 
@@ -60,7 +55,7 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
60
55
  end
61
56
 
62
57
  def visit_prop(node)
63
- node.name = node.name.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
58
+ node.name = node.name.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
64
59
  node.value = node.value.deep_copy
65
60
  yield
66
61
  end
@@ -71,7 +66,7 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
71
66
  end
72
67
 
73
68
  def visit_rule(node)
74
- node.rule = node.rule.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
69
+ node.rule = node.rule.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
75
70
  yield
76
71
  end
77
72
 
@@ -91,12 +86,12 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
91
86
  end
92
87
 
93
88
  def visit_directive(node)
94
- node.value = node.value.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
89
+ node.value = node.value.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
95
90
  yield
96
91
  end
97
92
 
98
93
  def visit_media(node)
99
- node.query = node.query.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
94
+ node.query = node.query.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}
100
95
  yield
101
96
  end
102
97
 
@@ -49,7 +49,7 @@ class Sass::Tree::Visitors::Extend < Sass::Tree::Visitors::Base
49
49
  def self.check_extends_fired!(extends)
50
50
  extends.each_value do |ex|
51
51
  next if ex.result == :succeeded || ex.node.optional?
52
- message = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"."
52
+ warn = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"."
53
53
  reason =
54
54
  if ex.result == :not_found
55
55
  "The selector \"#{ex.target.join}\" was not found."
@@ -57,12 +57,12 @@ class Sass::Tree::Visitors::Extend < Sass::Tree::Visitors::Base
57
57
  "No selectors matching \"#{ex.target.join}\" could be unified with \"#{ex.extender}\"."
58
58
  end
59
59
 
60
- # TODO(nweiz): this should use the Sass stack trace of the extend node.
61
- raise Sass::SyntaxError.new(<<MESSAGE, :filename => ex.node.filename, :line => ex.node.line)
62
- #{message}
63
- #{reason}
64
- Use "@extend #{ex.target.join} !optional" if the extend should be able to fail.
65
- MESSAGE
60
+ Sass::Util.sass_warn <<WARN
61
+ WARNING on line #{ex.node.line}#{" of #{ex.node.filename}" if ex.node.filename}: #{warn}
62
+ #{reason}
63
+ This will be an error in future releases of Sass.
64
+ Use "@extend #{ex.target.join} !optional" if the extend should be able to fail.
65
+ WARN
66
66
  end
67
67
  end
68
68
  end
@@ -1,155 +1,103 @@
1
1
  # A visitor for converting a dynamic Sass tree into a static Sass tree.
2
2
  class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
3
- class << self
4
- # @param root [Tree::Node] The root node of the tree to visit.
5
- # @param environment [Sass::Environment] The lexical environment.
6
- # @return [Tree::Node] The resulting tree of static nodes.
7
- def visit(root, environment = nil)
8
- new(environment).send(:visit, root)
9
- end
10
-
11
- # @api private
12
- # @comment
13
- # rubocop:disable MethodLength
14
- def perform_arguments(callable, args, splat)
15
- desc = "#{callable.type.capitalize} #{callable.name}"
16
- downcase_desc = "#{callable.type} #{callable.name}"
17
-
18
- # All keywords are contained in splat.keywords for consistency,
19
- # even if there were no splats passed in.
20
- old_keywords_accessed = splat.keywords_accessed
21
- keywords = splat.keywords
22
- splat.keywords_accessed = old_keywords_accessed
23
-
24
- begin
25
- unless keywords.empty?
26
- unknown_args = Sass::Util.array_minus(keywords.keys,
27
- callable.args.map {|var| var.first.underscored_name})
28
- if callable.splat && unknown_args.include?(callable.splat.underscored_name)
29
- raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} " +
30
- "cannot be used as a named argument.")
31
- elsif unknown_args.any?
32
- description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'
33
- raise Sass::SyntaxError.new("#{desc} doesn't have #{description} " +
34
- "#{unknown_args.map {|name| "$#{name}"}.join ', '}.")
35
- end
36
- end
37
- rescue Sass::SyntaxError => keyword_exception
38
- end
39
-
40
- # If there's no splat, raise the keyword exception immediately. The actual
41
- # raising happens in the ensure clause at the end of this function.
42
- return if keyword_exception && !callable.splat
43
-
44
- if args.size > callable.args.size && !callable.splat
45
- takes = callable.args.size
46
- passed = args.size
47
- raise Sass::SyntaxError.new(
48
- "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
49
- "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
50
- end
3
+ # @param root [Tree::Node] The root node of the tree to visit.
4
+ # @param environment [Sass::Environment] The lexical environment.
5
+ # @return [Tree::Node] The resulting tree of static nodes.
6
+ def self.visit(root, environment = Sass::Environment.new)
7
+ new(environment).send(:visit, root)
8
+ end
51
9
 
52
- splat_sep = :comma
53
- if splat
54
- args += splat.to_a
55
- splat_sep = splat.separator
56
- end
10
+ # @api private
11
+ def self.perform_arguments(callable, args, keywords, splat)
12
+ desc = "#{callable.type.capitalize} #{callable.name}"
13
+ downcase_desc = "#{callable.type} #{callable.name}"
57
14
 
58
- env = Sass::Environment.new(callable.environment)
59
- callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
60
- if value && keywords.has_key?(var.name)
61
- raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} " +
62
- "both by position and by name.")
15
+ begin
16
+ unless keywords.empty?
17
+ unknown_args = Sass::Util.array_minus(keywords.keys,
18
+ callable.args.map {|var| var.first.underscored_name})
19
+ if callable.splat && unknown_args.include?(callable.splat.underscored_name)
20
+ raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} cannot be used as a named argument.")
21
+ elsif unknown_args.any?
22
+ description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'
23
+ raise Sass::SyntaxError.new("#{desc} doesn't have #{description} #{unknown_args.map {|name| "$#{name}"}.join ', '}.")
63
24
  end
64
-
65
- value ||= keywords.delete(var.name)
66
- value ||= default && default.perform(env)
67
- raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
68
- env.set_local_var(var.name, value)
69
25
  end
26
+ rescue Sass::SyntaxError => keyword_exception
27
+ end
70
28
 
71
- if callable.splat
72
- rest = args[callable.args.length..-1] || []
73
- arg_list = Sass::Script::Value::ArgList.new(rest, keywords, splat_sep)
74
- arg_list.options = env.options
75
- env.set_local_var(callable.splat.name, arg_list)
76
- end
29
+ # If there's no splat, raise the keyword exception immediately. The actual
30
+ # raising happens in the ensure clause at the end of this function.
31
+ return if keyword_exception && !callable.splat
77
32
 
78
- yield env
79
- rescue StandardError => e
80
- ensure
81
- # If there's a keyword exception, we don't want to throw it immediately,
82
- # because the invalid keywords may be part of a glob argument that should be
83
- # passed on to another function. So we only raise it if we reach the end of
84
- # this function *and* the keywords attached to the argument list glob object
85
- # haven't been accessed.
86
- #
87
- # The keyword exception takes precedence over any Sass errors, but not over
88
- # non-Sass exceptions.
89
- if keyword_exception &&
90
- !(arg_list && arg_list.keywords_accessed) &&
91
- (e.nil? || e.is_a?(Sass::SyntaxError))
92
- raise keyword_exception
93
- elsif e
94
- raise e
95
- end
33
+ if args.size > callable.args.size && !callable.splat
34
+ takes = callable.args.size
35
+ passed = args.size
36
+ raise Sass::SyntaxError.new(
37
+ "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
38
+ "but #{passed} #{passed == 1 ? 'was' : 'were'} passed.")
96
39
  end
97
40
 
98
- # @api private
99
- # @return [Sass::Script::Value::ArgList]
100
- def perform_splat(splat, performed_keywords, kwarg_splat, environment)
101
- args, kwargs, separator = [], nil, :comma
102
-
103
- if splat
104
- splat = splat.perform(environment)
105
- separator = splat.separator || separator
106
- if splat.is_a?(Sass::Script::Value::ArgList)
107
- args = splat.to_a
108
- kwargs = splat.keywords
109
- elsif splat.is_a?(Sass::Script::Value::Map)
110
- kwargs = arg_hash(splat)
111
- else
112
- args = splat.to_a
113
- end
114
- end
115
- kwargs ||= Sass::Util::NormalizedMap.new
116
- kwargs.update(performed_keywords)
117
-
118
- if kwarg_splat
119
- kwarg_splat = kwarg_splat.perform(environment)
120
- unless kwarg_splat.is_a?(Sass::Script::Value::Map)
121
- raise Sass::SyntaxError.new("Variable keyword arguments must be a map " +
122
- "(was #{kwarg_splat.inspect}).")
123
- end
124
- kwargs.update(arg_hash(kwarg_splat))
41
+ splat_sep = :comma
42
+ if splat
43
+ args += splat.to_a
44
+ splat_sep = splat.separator if splat.is_a?(Sass::Script::List)
45
+ # If the splat argument exists, there won't be any keywords passed in
46
+ # manually, so we can safely overwrite rather than merge here.
47
+ keywords = splat.keywords if splat.is_a?(Sass::Script::ArgList)
48
+ end
49
+
50
+ keywords = keywords.dup
51
+ env = Sass::Environment.new(callable.environment)
52
+ callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
53
+ if value && keywords.include?(var.underscored_name)
54
+ raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} both by position and by name.")
125
55
  end
126
56
 
127
- Sass::Script::Value::ArgList.new(args, kwargs, separator)
57
+ value ||= keywords.delete(var.underscored_name)
58
+ value ||= default && default.perform(env)
59
+ raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
60
+ env.set_local_var(var.name, value)
128
61
  end
129
62
 
130
- private
63
+ if callable.splat
64
+ rest = args[callable.args.length..-1]
65
+ arg_list = Sass::Script::ArgList.new(rest, keywords.dup, splat_sep)
66
+ arg_list.options = env.options
67
+ env.set_local_var(callable.splat.name, arg_list)
68
+ end
131
69
 
132
- def arg_hash(map)
133
- Sass::Util.map_keys(map.to_h) do |key|
134
- next key.value if key.is_a?(Sass::Script::Value::String)
135
- raise Sass::SyntaxError.new("Variable keyword argument map must have string keys.\n" +
136
- "#{key.inspect} is not a string in #{map.inspect}.")
137
- end
70
+ yield env
71
+ rescue Exception => e
72
+ ensure
73
+ # If there's a keyword exception, we don't want to throw it immediately,
74
+ # because the invalid keywords may be part of a glob argument that should be
75
+ # passed on to another function. So we only raise it if we reach the end of
76
+ # this function *and* the keywords attached to the argument list glob object
77
+ # haven't been accessed.
78
+ #
79
+ # The keyword exception takes precedence over any Sass errors, but not over
80
+ # non-Sass exceptions.
81
+ if keyword_exception &&
82
+ !(arg_list && arg_list.keywords_accessed) &&
83
+ (e.nil? || e.is_a?(Sass::SyntaxError))
84
+ raise keyword_exception
85
+ elsif e
86
+ raise e
138
87
  end
139
88
  end
140
- # @comment
141
- # rubocop:enable MethodLength
142
89
 
143
90
  protected
144
91
 
145
92
  def initialize(env)
146
93
  @environment = env
94
+ # Stack trace information, including mixin includes and imports.
95
+ @stack = []
147
96
  end
148
97
 
149
98
  # If an exception is raised, this adds proper metadata to the backtrace.
150
99
  def visit(node)
151
- return super(node.dup) unless @environment
152
- @environment.stack.with_base(node.filename, node.line) {super(node.dup)}
100
+ super(node.dup)
153
101
  rescue Sass::SyntaxError => e
154
102
  e.modify_backtrace(:filename => node.filename, :line => node.line)
155
103
  raise e
@@ -194,11 +142,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
194
142
  # Prints the expression to STDERR.
195
143
  def visit_debug(node)
196
144
  res = node.expr.perform(@environment)
197
- if res.is_a?(Sass::Script::Value::String)
198
- res = res.value
199
- else
200
- res = res.to_sass
201
- end
145
+ res = res.value if res.is_a?(Sass::Script::String)
202
146
  if node.filename
203
147
  Sass::Util.sass_warn "#{node.filename}:#{node.line} DEBUG: #{res}"
204
148
  else
@@ -207,30 +151,13 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
207
151
  []
208
152
  end
209
153
 
210
- # Throws the expression as an error.
211
- def visit_error(node)
212
- res = node.expr.perform(@environment)
213
- if res.is_a?(Sass::Script::Value::String)
214
- res = res.value
215
- else
216
- res = res.to_sass
217
- end
218
- raise Sass::SyntaxError.new(res)
219
- end
220
-
221
154
  # Runs the child nodes once for each value in the list.
222
155
  def visit_each(node)
223
156
  list = node.list.perform(@environment)
224
157
 
225
158
  with_environment Sass::Environment.new(@environment) do
226
- list.to_a.map do |value|
227
- if node.vars.length == 1
228
- @environment.set_local_var(node.vars.first, value)
229
- else
230
- node.vars.zip(value.to_a) do |(var, sub_value)|
231
- @environment.set_local_var(var, sub_value || Sass::Script::Value::Null.new)
232
- end
233
- end
159
+ list.to_a.map do |v|
160
+ @environment.set_local_var(node.var, v)
234
161
  node.children.map {|c| visit(c)}
235
162
  end.flatten
236
163
  end
@@ -239,8 +166,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
239
166
  # Runs SassScript interpolation in the selector,
240
167
  # and then parses the result into a {Sass::Selector::CommaSequence}.
241
168
  def visit_extend(node)
242
- parser = Sass::SCSS::StaticParser.new(run_interp(node.selector),
243
- node.filename, node.options[:importer], node.line)
169
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.selector), node.filename, node.line)
244
170
  node.resolved_selector = parser.parse_selector
245
171
  node
246
172
  end
@@ -253,14 +179,12 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
253
179
  to.assert_int!
254
180
 
255
181
  to = to.coerce(from.numerator_units, from.denominator_units)
256
- direction = from.to_i > to.to_i ? -1 : 1
257
- range = Range.new(direction * from.to_i, direction * to.to_i, node.exclusive)
182
+ range = Range.new(from.to_i, to.to_i, node.exclusive)
258
183
 
259
184
  with_environment Sass::Environment.new(@environment) do
260
185
  range.map do |i|
261
186
  @environment.set_local_var(node.var,
262
- Sass::Script::Value::Number.new(direction * i,
263
- from.numerator_units, from.denominator_units))
187
+ Sass::Script::Number.new(i, from.numerator_units, from.denominator_units))
264
188
  node.children.map {|c| visit(c)}
265
189
  end.flatten
266
190
  end
@@ -270,8 +194,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
270
194
  def visit_function(node)
271
195
  env = Sass::Environment.new(@environment, node.options)
272
196
  @environment.set_local_function(node.name,
273
- Sass::Callable.new(node.name, node.args, node.splat, env,
274
- node.children, !:has_content, "function"))
197
+ Sass::Callable.new(node.name, node.args, node.splat, env, node.children, !:has_content, "function"))
275
198
  []
276
199
  end
277
200
 
@@ -291,84 +214,82 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
291
214
  # Returns a static DirectiveNode if this is importing a CSS file,
292
215
  # or parses and includes the imported Sass file.
293
216
  def visit_import(node)
294
- if (path = node.css_import?)
295
- resolved_node = Sass::Tree::CssImportNode.resolved("url(#{path})")
296
- resolved_node.source_range = node.source_range
297
- return resolved_node
217
+ if path = node.css_import?
218
+ return Sass::Tree::CssImportNode.resolved("url(#{path})")
298
219
  end
299
220
  file = node.imported_file
300
- if @environment.stack.frames.any? {|f| f.is_import? && f.filename == file.options[:filename]}
301
- handle_import_loop!(node)
302
- end
221
+ handle_import_loop!(node) if @stack.any? {|e| e[:filename] == file.options[:filename]}
303
222
 
304
223
  begin
305
- @environment.stack.with_import(node.filename, node.line) do
306
- root = file.to_tree
307
- Sass::Tree::Visitors::CheckNesting.visit(root)
308
- node.children = root.children.map {|c| visit(c)}.flatten
309
- node
310
- end
224
+ @stack.push(:filename => node.filename, :line => node.line)
225
+ root = file.to_tree
226
+ Sass::Tree::Visitors::CheckNesting.visit(root)
227
+ node.children = root.children.map {|c| visit(c)}.flatten
228
+ node
311
229
  rescue Sass::SyntaxError => e
312
230
  e.modify_backtrace(:filename => node.imported_file.options[:filename])
313
231
  e.add_backtrace(:filename => node.filename, :line => node.line)
314
232
  raise e
315
233
  end
234
+ ensure
235
+ @stack.pop unless path
316
236
  end
317
237
 
318
238
  # Loads a mixin into the environment.
319
239
  def visit_mixindef(node)
320
240
  env = Sass::Environment.new(@environment, node.options)
321
241
  @environment.set_local_mixin(node.name,
322
- Sass::Callable.new(node.name, node.args, node.splat, env,
323
- node.children, node.has_content, "mixin"))
242
+ Sass::Callable.new(node.name, node.args, node.splat, env, node.children, node.has_content, "mixin"))
324
243
  []
325
244
  end
326
245
 
327
246
  # Runs a mixin.
328
247
  def visit_mixin(node)
329
- @environment.stack.with_mixin(node.filename, node.line, node.name) do
330
- mixin = @environment.mixin(node.name)
331
- raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin
248
+ include_loop = true
249
+ handle_include_loop!(node) if @stack.any? {|e| e[:name] == node.name}
250
+ include_loop = false
332
251
 
333
- if node.children.any? && !mixin.has_content
334
- raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
335
- end
252
+ @stack.push(:filename => node.filename, :line => node.line, :name => node.name)
253
+ raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin = @environment.mixin(node.name)
336
254
 
337
- args = node.args.map {|a| a.perform(@environment)}
338
- keywords = Sass::Util.map_vals(node.keywords) {|v| v.perform(@environment)}
339
- splat = self.class.perform_splat(node.splat, keywords, node.kwarg_splat, @environment)
255
+ if node.children.any? && !mixin.has_content
256
+ raise Sass::SyntaxError.new(%Q{Mixin "#{node.name}" does not accept a content block.})
257
+ end
340
258
 
341
- self.class.perform_arguments(mixin, args, splat) do |env|
342
- env.caller = Sass::Environment.new(@environment)
343
- env.content = [node.children, @environment] if node.has_children
259
+ args = node.args.map {|a| a.perform(@environment)}
260
+ keywords = Sass::Util.map_hash(node.keywords) {|k, v| [k, v.perform(@environment)]}
261
+ splat = node.splat.perform(@environment) if node.splat
344
262
 
345
- trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
346
- with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
347
- trace_node
348
- end
263
+ self.class.perform_arguments(mixin, args, keywords, splat) do |env|
264
+ env.caller = Sass::Environment.new(@environment)
265
+ env.content = node.children if node.has_children
266
+
267
+ trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
268
+ with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
269
+ trace_node
349
270
  end
350
271
  rescue Sass::SyntaxError => e
351
- e.modify_backtrace(:mixin => node.name, :line => node.line)
352
- e.add_backtrace(:line => node.line)
272
+ unless include_loop
273
+ e.modify_backtrace(:mixin => node.name, :line => node.line)
274
+ e.add_backtrace(:line => node.line)
275
+ end
353
276
  raise e
277
+ ensure
278
+ @stack.pop unless include_loop
354
279
  end
355
280
 
356
281
  def visit_content(node)
357
- content, content_env = @environment.content
358
- return [] unless content
359
- @environment.stack.with_mixin(node.filename, node.line, '@content') do
360
- trace_node = Sass::Tree::TraceNode.from_node('@content', node)
361
- content_env = Sass::Environment.new(content_env)
362
- content_env.caller = Sass::Environment.new(@environment)
363
- with_environment(content_env) do
364
- trace_node.children = content.map {|c| visit(c.dup)}.flatten
365
- end
366
- trace_node
367
- end
282
+ return [] unless content = @environment.content
283
+ @stack.push(:filename => node.filename, :line => node.line, :name => '@content')
284
+ trace_node = Sass::Tree::TraceNode.from_node('@content', node)
285
+ with_environment(@environment.caller) {trace_node.children = content.map {|c| visit(c.dup)}.flatten}
286
+ trace_node
368
287
  rescue Sass::SyntaxError => e
369
288
  e.modify_backtrace(:mixin => '@content', :line => node.line)
370
289
  e.add_backtrace(:line => node.line)
371
290
  raise e
291
+ ensure
292
+ @stack.pop if content
372
293
  end
373
294
 
374
295
  # Runs any SassScript that may be embedded in a property.
@@ -376,7 +297,6 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
376
297
  node.resolved_name = run_interp(node.name)
377
298
  val = node.value.perform(@environment)
378
299
  node.resolved_value = val.to_s
379
- node.value_source_range = val.source_range if val.source_range
380
300
  yield
381
301
  end
382
302
 
@@ -388,83 +308,38 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
388
308
  # Runs SassScript interpolation in the selector,
389
309
  # and then parses the result into a {Sass::Selector::CommaSequence}.
390
310
  def visit_rule(node)
391
- old_at_root_without_rule = @at_root_without_rule
392
- parser = Sass::SCSS::StaticParser.new(run_interp(node.rule),
393
- node.filename, node.options[:importer], node.line)
394
- if @in_keyframes
395
- keyframe_rule_node = Sass::Tree::KeyframeRuleNode.new(parser.parse_keyframes_selector)
396
- keyframe_rule_node.options = node.options
397
- keyframe_rule_node.line = node.line
398
- keyframe_rule_node.filename = node.filename
399
- keyframe_rule_node.source_range = node.source_range
400
- with_environment Sass::Environment.new(@environment, node.options) do
401
- keyframe_rule_node.children = node.children.map {|c| visit(c)}.flatten
402
- end
403
- keyframe_rule_node
404
- else
405
- @at_root_without_rule = false
406
- node.parsed_rules ||= parser.parse_selector
407
- node.resolved_rules = node.parsed_rules.resolve_parent_refs(
408
- @environment.selector, !old_at_root_without_rule)
409
- node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
410
- with_environment Sass::Environment.new(@environment, node.options) do
411
- @environment.selector = node.resolved_rules
412
- node.children = node.children.map {|c| visit(c)}.flatten
413
- end
414
- node
311
+ rule = node.rule
312
+ rule = rule.map {|e| e.is_a?(String) && e != ' ' ? e.strip : e} if node.style == :compressed
313
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.filename, node.line)
314
+ node.parsed_rules ||= parser.parse_selector
315
+ if node.options[:trace_selectors]
316
+ @stack.push(:filename => node.filename, :line => node.line)
317
+ node.stack_trace = stack_trace
318
+ @stack.pop
415
319
  end
416
- ensure
417
- @at_root_without_rule = old_at_root_without_rule
418
- end
419
-
420
- # Sets a variable that indicates that the first level of rule nodes
421
- # shouldn't include the parent selector by default.
422
- def visit_atroot(node)
423
- if node.query
424
- parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
425
- node.filename, node.options[:importer], node.line)
426
- node.resolved_type, node.resolved_value = parser.parse_static_at_root_query
427
- else
428
- node.resolved_type, node.resolved_value = :without, ['rule']
429
- end
430
-
431
- old_at_root_without_rule = @at_root_without_rule
432
- old_in_keyframes = @in_keyframes
433
- @at_root_without_rule = true if node.exclude?('rule')
434
- @in_keyframes = false if node.exclude?('keyframes')
435
320
  yield
436
- ensure
437
- @in_keyframes = old_in_keyframes
438
- @at_root_without_rule = old_at_root_without_rule
439
321
  end
440
322
 
441
323
  # Loads the new variable value into the environment.
442
324
  def visit_variable(node)
443
- env = @environment
444
- env = env.global_env if node.global
445
- if node.guarded
446
- var = env.var(node.name)
447
- return [] if var && !var.null?
448
- end
449
-
325
+ var = @environment.var(node.name)
326
+ return [] if node.guarded && var && !var.null?
450
327
  val = node.expr.perform(@environment)
451
- if node.expr.source_range
452
- val.source_range = node.expr.source_range
453
- else
454
- val.source_range = node.source_range
455
- end
456
- env.set_var(node.name, val)
328
+ @environment.set_var(node.name, val)
457
329
  []
458
330
  end
459
331
 
460
332
  # Prints the expression to STDERR with a stylesheet trace.
461
333
  def visit_warn(node)
334
+ @stack.push(:filename => node.filename, :line => node.line)
462
335
  res = node.expr.perform(@environment)
463
- res = res.value if res.is_a?(Sass::Script::Value::String)
336
+ res = res.value if res.is_a?(Sass::Script::String)
464
337
  msg = "WARNING: #{res}\n "
465
- msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n"
338
+ msg << stack_trace.join("\n ") << "\n"
466
339
  Sass::Util.sass_warn msg
467
340
  []
341
+ ensure
342
+ @stack.pop
468
343
  end
469
344
 
470
345
  # Runs the child nodes until the continuation expression becomes false.
@@ -478,18 +353,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
478
353
 
479
354
  def visit_directive(node)
480
355
  node.resolved_value = run_interp(node.value)
481
- old_in_keyframes, @in_keyframes = @in_keyframes, node.normalized_name == "@keyframes"
482
- with_environment Sass::Environment.new(@environment) do
483
- node.children = node.children.map {|c| visit(c)}.flatten
484
- node
485
- end
486
- ensure
487
- @in_keyframes = old_in_keyframes
356
+ yield
488
357
  end
489
358
 
490
359
  def visit_media(node)
491
- parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
492
- node.filename, node.options[:importer], node.line)
360
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query), node.filename, node.line)
493
361
  node.resolved_query ||= parser.parse_media_query_list
494
362
  yield
495
363
  end
@@ -502,9 +370,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
502
370
 
503
371
  def visit_cssimport(node)
504
372
  node.resolved_uri = run_interp([node.uri])
505
- if node.query && !node.query.empty?
506
- parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
507
- node.filename, node.options[:importer], node.line)
373
+ if node.query
374
+ parser = Sass::SCSS::StaticParser.new(run_interp(node.query), node.filename, node.line)
508
375
  node.resolved_query ||= parser.parse_media_query_list
509
376
  end
510
377
  yield
@@ -512,10 +379,26 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
512
379
 
513
380
  private
514
381
 
382
+ def stack_trace
383
+ trace = []
384
+ stack = @stack.map {|e| e.dup}.reverse
385
+ stack.each_cons(2) {|(e1, e2)| e1[:caller] = e2[:name]; [e1, e2]}
386
+ stack.each_with_index do |entry, i|
387
+ msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
388
+ msg << " of #{entry[:filename] || "an unknown file"}"
389
+ msg << ", in `#{entry[:caller]}'" if entry[:caller]
390
+ trace << msg
391
+ end
392
+ trace
393
+ end
394
+
515
395
  def run_interp_no_strip(text)
516
396
  text.map do |r|
517
397
  next r if r.is_a?(String)
518
- r.perform(@environment).to_s(:quote => :none)
398
+ val = r.perform(@environment)
399
+ # Interpolated strings should never render with quotes
400
+ next val.value if val.is_a?(Sass::Script::String)
401
+ val.to_s
519
402
  end.join
520
403
  end
521
404
 
@@ -523,9 +406,33 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
523
406
  run_interp_no_strip(text).strip
524
407
  end
525
408
 
409
+ def handle_include_loop!(node)
410
+ msg = "An @include loop has been found:"
411
+ content_count = 0
412
+ mixins = @stack.reverse.map {|s| s[:name]}.compact.select do |s|
413
+ if s == '@content'
414
+ content_count += 1
415
+ false
416
+ elsif content_count > 0
417
+ content_count -= 1
418
+ false
419
+ else
420
+ true
421
+ end
422
+ end
423
+
424
+ return unless mixins.include?(node.name)
425
+ raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
426
+
427
+ msg << "\n" << Sass::Util.enum_cons(mixins.reverse + [node.name], 2).map do |m1, m2|
428
+ " #{m1} includes #{m2}"
429
+ end.join("\n")
430
+ raise Sass::SyntaxError.new(msg)
431
+ end
432
+
526
433
  def handle_import_loop!(node)
527
434
  msg = "An @import loop has been found:"
528
- files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
435
+ files = @stack.map {|s| s[:filename]}.compact
529
436
  if node.filename == node.imported_file.options[:filename]
530
437
  raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself")
531
438
  end