oreorenasass 3.4.4 → 3.4.5

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