sass4 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +13 -0
  3. data/AGENTS.md +534 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/CONTRIBUTING.md +148 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +242 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/sass-spec-ref.sh +40 -0
  14. data/extra/update_watch.rb +13 -0
  15. data/init.rb +18 -0
  16. data/lib/sass/cache_stores/base.rb +88 -0
  17. data/lib/sass/cache_stores/chain.rb +34 -0
  18. data/lib/sass/cache_stores/filesystem.rb +60 -0
  19. data/lib/sass/cache_stores/memory.rb +46 -0
  20. data/lib/sass/cache_stores/null.rb +25 -0
  21. data/lib/sass/cache_stores.rb +15 -0
  22. data/lib/sass/callbacks.rb +67 -0
  23. data/lib/sass/css.rb +407 -0
  24. data/lib/sass/deprecation.rb +55 -0
  25. data/lib/sass/engine.rb +1236 -0
  26. data/lib/sass/environment.rb +236 -0
  27. data/lib/sass/error.rb +198 -0
  28. data/lib/sass/exec/base.rb +188 -0
  29. data/lib/sass/exec/sass_convert.rb +283 -0
  30. data/lib/sass/exec/sass_scss.rb +436 -0
  31. data/lib/sass/exec.rb +9 -0
  32. data/lib/sass/features.rb +48 -0
  33. data/lib/sass/importers/base.rb +182 -0
  34. data/lib/sass/importers/deprecated_path.rb +51 -0
  35. data/lib/sass/importers/filesystem.rb +221 -0
  36. data/lib/sass/importers.rb +23 -0
  37. data/lib/sass/logger/base.rb +47 -0
  38. data/lib/sass/logger/delayed.rb +50 -0
  39. data/lib/sass/logger/log_level.rb +45 -0
  40. data/lib/sass/logger.rb +17 -0
  41. data/lib/sass/media.rb +210 -0
  42. data/lib/sass/plugin/compiler.rb +552 -0
  43. data/lib/sass/plugin/configuration.rb +134 -0
  44. data/lib/sass/plugin/generic.rb +15 -0
  45. data/lib/sass/plugin/merb.rb +48 -0
  46. data/lib/sass/plugin/rack.rb +60 -0
  47. data/lib/sass/plugin/rails.rb +47 -0
  48. data/lib/sass/plugin/staleness_checker.rb +199 -0
  49. data/lib/sass/plugin.rb +134 -0
  50. data/lib/sass/railtie.rb +10 -0
  51. data/lib/sass/repl.rb +57 -0
  52. data/lib/sass/root.rb +7 -0
  53. data/lib/sass/script/css_lexer.rb +33 -0
  54. data/lib/sass/script/css_parser.rb +36 -0
  55. data/lib/sass/script/functions.rb +3103 -0
  56. data/lib/sass/script/lexer.rb +518 -0
  57. data/lib/sass/script/parser.rb +1164 -0
  58. data/lib/sass/script/tree/funcall.rb +314 -0
  59. data/lib/sass/script/tree/interpolation.rb +220 -0
  60. data/lib/sass/script/tree/list_literal.rb +119 -0
  61. data/lib/sass/script/tree/literal.rb +49 -0
  62. data/lib/sass/script/tree/map_literal.rb +64 -0
  63. data/lib/sass/script/tree/node.rb +119 -0
  64. data/lib/sass/script/tree/operation.rb +149 -0
  65. data/lib/sass/script/tree/selector.rb +26 -0
  66. data/lib/sass/script/tree/string_interpolation.rb +125 -0
  67. data/lib/sass/script/tree/unary_operation.rb +69 -0
  68. data/lib/sass/script/tree/variable.rb +57 -0
  69. data/lib/sass/script/tree.rb +16 -0
  70. data/lib/sass/script/value/arg_list.rb +36 -0
  71. data/lib/sass/script/value/base.rb +258 -0
  72. data/lib/sass/script/value/bool.rb +35 -0
  73. data/lib/sass/script/value/callable.rb +25 -0
  74. data/lib/sass/script/value/color.rb +704 -0
  75. data/lib/sass/script/value/function.rb +19 -0
  76. data/lib/sass/script/value/helpers.rb +298 -0
  77. data/lib/sass/script/value/list.rb +135 -0
  78. data/lib/sass/script/value/map.rb +70 -0
  79. data/lib/sass/script/value/null.rb +44 -0
  80. data/lib/sass/script/value/number.rb +564 -0
  81. data/lib/sass/script/value/string.rb +138 -0
  82. data/lib/sass/script/value.rb +13 -0
  83. data/lib/sass/script.rb +66 -0
  84. data/lib/sass/scss/css_parser.rb +61 -0
  85. data/lib/sass/scss/parser.rb +1343 -0
  86. data/lib/sass/scss/rx.rb +134 -0
  87. data/lib/sass/scss/static_parser.rb +351 -0
  88. data/lib/sass/scss.rb +14 -0
  89. data/lib/sass/selector/abstract_sequence.rb +112 -0
  90. data/lib/sass/selector/comma_sequence.rb +195 -0
  91. data/lib/sass/selector/pseudo.rb +291 -0
  92. data/lib/sass/selector/sequence.rb +661 -0
  93. data/lib/sass/selector/simple.rb +124 -0
  94. data/lib/sass/selector/simple_sequence.rb +348 -0
  95. data/lib/sass/selector.rb +327 -0
  96. data/lib/sass/shared.rb +76 -0
  97. data/lib/sass/source/map.rb +209 -0
  98. data/lib/sass/source/position.rb +39 -0
  99. data/lib/sass/source/range.rb +41 -0
  100. data/lib/sass/stack.rb +140 -0
  101. data/lib/sass/supports.rb +225 -0
  102. data/lib/sass/tree/at_root_node.rb +83 -0
  103. data/lib/sass/tree/charset_node.rb +22 -0
  104. data/lib/sass/tree/comment_node.rb +82 -0
  105. data/lib/sass/tree/content_node.rb +9 -0
  106. data/lib/sass/tree/css_import_node.rb +68 -0
  107. data/lib/sass/tree/debug_node.rb +18 -0
  108. data/lib/sass/tree/directive_node.rb +59 -0
  109. data/lib/sass/tree/each_node.rb +24 -0
  110. data/lib/sass/tree/error_node.rb +18 -0
  111. data/lib/sass/tree/extend_node.rb +43 -0
  112. data/lib/sass/tree/for_node.rb +36 -0
  113. data/lib/sass/tree/function_node.rb +44 -0
  114. data/lib/sass/tree/if_node.rb +52 -0
  115. data/lib/sass/tree/import_node.rb +75 -0
  116. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  117. data/lib/sass/tree/media_node.rb +48 -0
  118. data/lib/sass/tree/mixin_def_node.rb +38 -0
  119. data/lib/sass/tree/mixin_node.rb +52 -0
  120. data/lib/sass/tree/node.rb +240 -0
  121. data/lib/sass/tree/prop_node.rb +162 -0
  122. data/lib/sass/tree/return_node.rb +19 -0
  123. data/lib/sass/tree/root_node.rb +44 -0
  124. data/lib/sass/tree/rule_node.rb +153 -0
  125. data/lib/sass/tree/supports_node.rb +38 -0
  126. data/lib/sass/tree/trace_node.rb +33 -0
  127. data/lib/sass/tree/variable_node.rb +36 -0
  128. data/lib/sass/tree/visitors/base.rb +72 -0
  129. data/lib/sass/tree/visitors/check_nesting.rb +173 -0
  130. data/lib/sass/tree/visitors/convert.rb +350 -0
  131. data/lib/sass/tree/visitors/cssize.rb +362 -0
  132. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  133. data/lib/sass/tree/visitors/extend.rb +64 -0
  134. data/lib/sass/tree/visitors/perform.rb +572 -0
  135. data/lib/sass/tree/visitors/set_options.rb +139 -0
  136. data/lib/sass/tree/visitors/to_css.rb +440 -0
  137. data/lib/sass/tree/warn_node.rb +18 -0
  138. data/lib/sass/tree/while_node.rb +18 -0
  139. data/lib/sass/util/multibyte_string_scanner.rb +151 -0
  140. data/lib/sass/util/normalized_map.rb +122 -0
  141. data/lib/sass/util/subset_map.rb +109 -0
  142. data/lib/sass/util/test.rb +9 -0
  143. data/lib/sass/util.rb +1137 -0
  144. data/lib/sass/version.rb +120 -0
  145. data/lib/sass.rb +102 -0
  146. data/rails/init.rb +1 -0
  147. metadata +283 -0
@@ -0,0 +1,314 @@
1
+ require 'sass/script/functions'
2
+ require 'sass/util'
3
+
4
+ module Sass::Script::Tree
5
+ # A SassScript parse node representing a function call.
6
+ #
7
+ # A function call either calls one of the functions in
8
+ # {Sass::Script::Functions}, or if no function with the given name exists it
9
+ # returns a string representation of the function call.
10
+ class Funcall < Node
11
+ # The name of the function.
12
+ #
13
+ # @return [String]
14
+ attr_reader :name
15
+
16
+ # The callable to be invoked
17
+ #
18
+ # @return [Sass::Callable] or nil if no callable is provided.
19
+ attr_reader :callable
20
+
21
+ # The arguments to the function.
22
+ #
23
+ # @return [Array<Node>]
24
+ attr_reader :args
25
+
26
+ # The keyword arguments to the function.
27
+ #
28
+ # @return [Sass::Util::NormalizedMap<Node>]
29
+ attr_reader :keywords
30
+
31
+ # The first splat argument for this function, if one exists.
32
+ #
33
+ # This could be a list of positional arguments, a map of keyword
34
+ # arguments, or an arglist containing both.
35
+ #
36
+ # @return [Node?]
37
+ attr_accessor :splat
38
+
39
+ # The second splat argument for this function, if one exists.
40
+ #
41
+ # If this exists, it's always a map of keyword arguments, and
42
+ # \{#splat} is always either a list or an arglist.
43
+ #
44
+ # @return [Node?]
45
+ attr_accessor :kwarg_splat
46
+
47
+ # @param name_or_callable [String, Sass::Callable] See \{#name}
48
+ # @param args [Array<Node>] See \{#args}
49
+ # @param keywords [Sass::Util::NormalizedMap<Node>] See \{#keywords}
50
+ # @param splat [Node] See \{#splat}
51
+ # @param kwarg_splat [Node] See \{#kwarg_splat}
52
+ def initialize(name_or_callable, args, keywords, splat, kwarg_splat)
53
+ if name_or_callable.is_a?(Sass::Callable)
54
+ @callable = name_or_callable
55
+ @name = name_or_callable.name
56
+ else
57
+ @callable = nil
58
+ @name = name_or_callable
59
+ end
60
+ @args = args
61
+ @keywords = keywords
62
+ @splat = splat
63
+ @kwarg_splat = kwarg_splat
64
+ super()
65
+ end
66
+
67
+ # @return [String] A string representation of the function call
68
+ def inspect
69
+ args = @args.map {|a| a.inspect}.join(', ')
70
+ keywords = @keywords.as_stored.to_a.map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
71
+ if self.splat
72
+ splat = args.empty? && keywords.empty? ? "" : ", "
73
+ splat = "#{splat}#{self.splat.inspect}..."
74
+ splat = "#{splat}, #{kwarg_splat.inspect}..." if kwarg_splat
75
+ end
76
+ "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
77
+ end
78
+
79
+ # @see Node#to_sass
80
+ def to_sass(opts = {})
81
+ arg_to_sass = lambda do |arg|
82
+ sass = arg.to_sass(opts)
83
+ sass = "(#{sass})" if arg.is_a?(Sass::Script::Tree::ListLiteral) && arg.separator == :comma
84
+ sass
85
+ end
86
+
87
+ args = @args.map(&arg_to_sass)
88
+ keywords = @keywords.as_stored.to_a.map {|k, v| "$#{dasherize(k, opts)}: #{arg_to_sass[v]}"}
89
+
90
+ if self.splat
91
+ splat = "#{arg_to_sass[self.splat]}..."
92
+ kwarg_splat = "#{arg_to_sass[self.kwarg_splat]}..." if self.kwarg_splat
93
+ end
94
+
95
+ arglist = [args, splat, keywords, kwarg_splat].flatten.compact.join(', ')
96
+ "#{dasherize(name, opts)}(#{arglist})"
97
+ end
98
+
99
+ # Returns the arguments to the function.
100
+ #
101
+ # @return [Array<Node>]
102
+ # @see Node#children
103
+ def children
104
+ res = @args + @keywords.values
105
+ res << @splat if @splat
106
+ res << @kwarg_splat if @kwarg_splat
107
+ res
108
+ end
109
+
110
+ # @see Node#deep_copy
111
+ def deep_copy
112
+ node = dup
113
+ node.instance_variable_set('@args', args.map {|a| a.deep_copy})
114
+ copied_keywords = Sass::Util::NormalizedMap.new
115
+ @keywords.as_stored.each {|k, v| copied_keywords[k] = v.deep_copy}
116
+ node.instance_variable_set('@keywords', copied_keywords)
117
+ node
118
+ end
119
+
120
+ protected
121
+
122
+ # Evaluates the function call.
123
+ #
124
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
125
+ # @return [Sass::Script::Value] The SassScript object that is the value of the function call
126
+ # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
127
+ def _perform(environment)
128
+ args = @args.each_with_index.
129
+ map {|a, i| perform_arg(a, environment, signature && signature.args[i])}
130
+ keywords = Sass::Util.map_hash(@keywords) do |k, v|
131
+ [k, perform_arg(v, environment, k.tr('-', '_'))]
132
+ end
133
+ splat = Sass::Tree::Visitors::Perform.perform_splat(
134
+ @splat, keywords, @kwarg_splat, environment)
135
+
136
+ fn = @callable || environment.function(@name)
137
+
138
+ if fn && fn.origin == :stylesheet
139
+ environment.stack.with_function(filename, line, name) do
140
+ return without_original(perform_sass_fn(fn, args, splat, environment))
141
+ end
142
+ end
143
+
144
+ args = construct_ruby_args(ruby_name, args, splat, environment)
145
+
146
+ if Sass::Script::Functions.callable?(ruby_name) && (!fn || fn.origin == :builtin)
147
+ local_environment = Sass::Environment.new(environment.global_env, environment.options)
148
+ local_environment.caller = Sass::ReadOnlyEnvironment.new(environment, environment.options)
149
+ result = local_environment.stack.with_function(filename, line, name) do
150
+ opts(Sass::Script::Functions::EvaluationContext.new(
151
+ local_environment).send(ruby_name, *args))
152
+ end
153
+ without_original(result)
154
+ else
155
+ opts(to_literal(args))
156
+ end
157
+ rescue ArgumentError => e
158
+ reformat_argument_error(e)
159
+ end
160
+
161
+ # Compass historically overrode this before it changed name to {Funcall#to_value}.
162
+ # We should get rid of it in the future.
163
+ def to_literal(args)
164
+ to_value(args)
165
+ end
166
+
167
+ # This method is factored out from `_perform` so that compass can override
168
+ # it with a cross-browser implementation for functions that require vendor prefixes
169
+ # in the generated css.
170
+ def to_value(args)
171
+ Sass::Script::Value::String.new("#{name}(#{args.join(', ')})")
172
+ end
173
+
174
+ private
175
+
176
+ def ruby_name
177
+ @ruby_name ||= @name.tr('-', '_')
178
+ end
179
+
180
+ def perform_arg(argument, environment, name)
181
+ return argument if signature && signature.delayed_args.include?(name)
182
+ argument.perform(environment)
183
+ end
184
+
185
+ def signature
186
+ @signature ||= Sass::Script::Functions.signature(name.to_sym, @args.size, @keywords.size)
187
+ end
188
+
189
+ def without_original(value)
190
+ return value unless value.is_a?(Sass::Script::Value::Number)
191
+ value = value.dup
192
+ value.original = nil
193
+ value
194
+ end
195
+
196
+ def construct_ruby_args(name, args, splat, environment)
197
+ args += splat.to_a if splat
198
+
199
+ # All keywords are contained in splat.keywords for consistency,
200
+ # even if there were no splats passed in.
201
+ old_keywords_accessed = splat.keywords_accessed
202
+ keywords = splat.keywords
203
+ splat.keywords_accessed = old_keywords_accessed
204
+
205
+ unless (signature = Sass::Script::Functions.signature(name.to_sym, args.size, keywords.size))
206
+ return args if keywords.empty?
207
+ raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
208
+ end
209
+
210
+ # If the user passes more non-keyword args than the function expects,
211
+ # but it does expect keyword args, Ruby's arg handling won't raise an error.
212
+ # Since we don't want to make functions think about this,
213
+ # we'll handle it for them here.
214
+ if signature.var_kwargs && !signature.var_args && args.size > signature.args.size
215
+ raise Sass::SyntaxError.new(
216
+ "#{args[signature.args.size].inspect} is not a keyword argument for `#{name}'")
217
+ elsif keywords.empty?
218
+ args << {} if signature.var_kwargs
219
+ return args
220
+ end
221
+
222
+ argnames = signature.args[args.size..-1] || []
223
+ deprecated_argnames = (signature.deprecated && signature.deprecated[args.size..-1]) || []
224
+ args += argnames.zip(deprecated_argnames).map do |(argname, deprecated_argname)|
225
+ if keywords.has_key?(argname)
226
+ keywords.delete(argname)
227
+ elsif deprecated_argname && keywords.has_key?(deprecated_argname)
228
+ deprecated_argname = keywords.denormalize(deprecated_argname)
229
+ Sass::Util.sass_warn("DEPRECATION WARNING: The `$#{deprecated_argname}' argument for " +
230
+ "`#{@name}()' has been renamed to `$#{argname}'.")
231
+ keywords.delete(deprecated_argname)
232
+ else
233
+ raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}")
234
+ end
235
+ end
236
+
237
+ if keywords.size > 0
238
+ if signature.var_kwargs
239
+ # Don't pass a NormalizedMap to a Ruby function.
240
+ args << keywords.to_hash
241
+ else
242
+ argname = keywords.keys.sort.first
243
+ if signature.args.include?(argname)
244
+ raise Sass::SyntaxError.new(
245
+ "Function #{name} was passed argument $#{argname} both by position and by name")
246
+ else
247
+ raise Sass::SyntaxError.new(
248
+ "Function #{name} doesn't have an argument named $#{argname}")
249
+ end
250
+ end
251
+ end
252
+
253
+ args
254
+ end
255
+
256
+ def perform_sass_fn(function, args, splat, environment)
257
+ Sass::Tree::Visitors::Perform.perform_arguments(function, args, splat, environment) do |env|
258
+ env.caller = Sass::Environment.new(environment)
259
+
260
+ val = catch :_sass_return do
261
+ function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, env)}
262
+ raise Sass::SyntaxError.new("Function #{@name} finished without @return")
263
+ end
264
+ val
265
+ end
266
+ end
267
+
268
+ def reformat_argument_error(e)
269
+ message = e.message
270
+
271
+ # If this is a legitimate Ruby-raised argument error, re-raise it.
272
+ # Otherwise, it's an error in the user's stylesheet, so wrap it.
273
+ if Sass::Util.rbx?
274
+ # Rubinius has a different error report string than vanilla Ruby. It
275
+ # also doesn't put the actual method for which the argument error was
276
+ # thrown in the backtrace, nor does it include `send`, so we look for
277
+ # `_perform`.
278
+ if e.message =~ /^method '([^']+)': given (\d+), expected (\d+)/
279
+ error_name, given, expected = $1, $2, $3
280
+ raise e if error_name != ruby_name || e.backtrace[0] !~ /:in `_perform'$/
281
+ message = "wrong number of arguments (#{given} for #{expected})"
282
+ end
283
+ elsif Sass::Util.jruby?
284
+ should_maybe_raise =
285
+ e.message =~ /^wrong number of arguments calling `[^`]+` \((\d+) for (\d+)\)/
286
+ given, expected = $1, $2
287
+
288
+ if should_maybe_raise
289
+ # JRuby 1.7 includes __send__ before send and _perform.
290
+ trace = e.backtrace.dup
291
+ raise e if trace.shift !~ /:in `__send__'$/
292
+
293
+ # JRuby (as of 1.7.2) doesn't put the actual method
294
+ # for which the argument error was thrown in the backtrace, so we
295
+ # detect whether our send threw an argument error.
296
+ if !(trace[0] =~ /:in `send'$/ && trace[1] =~ /:in `_perform'$/)
297
+ raise e
298
+ else
299
+ # JRuby 1.7 doesn't use standard formatting for its ArgumentErrors.
300
+ message = "wrong number of arguments (#{given} for #{expected})"
301
+ end
302
+ end
303
+ elsif (md = /^wrong number of arguments \(given (\d+), expected (\d+)\)/.match(e.message)) &&
304
+ e.backtrace[0] =~ /:in `#{ruby_name}'$/
305
+ # Handle ruby 2.3 error formatting
306
+ message = "wrong number of arguments (#{md[1]} for #{md[2]})"
307
+ elsif e.message =~ /^wrong number of arguments/ &&
308
+ e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
309
+ raise e
310
+ end
311
+ raise Sass::SyntaxError.new("#{message} for `#{name}'")
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,220 @@
1
+ module Sass::Script::Tree
2
+ # A SassScript object representing `#{}` interpolation outside a string.
3
+ #
4
+ # @see StringInterpolation
5
+ class Interpolation < Node
6
+ # @return [Node] The SassScript before the interpolation
7
+ attr_reader :before
8
+
9
+ # @return [Node] The SassScript within the interpolation
10
+ attr_reader :mid
11
+
12
+ # @return [Node] The SassScript after the interpolation
13
+ attr_reader :after
14
+
15
+ # @return [Boolean] Whether there was whitespace between `before` and `#{`
16
+ attr_reader :whitespace_before
17
+
18
+ # @return [Boolean] Whether there was whitespace between `}` and `after`
19
+ attr_reader :whitespace_after
20
+
21
+ # @return [Boolean] Whether the original format of the interpolation was
22
+ # plain text, not an interpolation. This is used when converting back to
23
+ # SassScript.
24
+ attr_reader :originally_text
25
+
26
+ # @return [Boolean] Whether a color value passed to the interpolation should
27
+ # generate a warning.
28
+ attr_reader :warn_for_color
29
+
30
+ # The type of interpolation deprecation for this node.
31
+ #
32
+ # This can be `:none`, indicating that the node doesn't use deprecated
33
+ # interpolation; `:immediate`, indicating that a deprecation warning should
34
+ # be emitted as soon as possible; or `:potential`, indicating that a
35
+ # deprecation warning should be emitted if the resulting string is used in a
36
+ # way that would distinguish it from a list.
37
+ #
38
+ # @return [Symbol]
39
+ attr_reader :deprecation
40
+
41
+ # Interpolation in a property is of the form `before #{mid} after`.
42
+ #
43
+ # @param before [Node] See {Interpolation#before}
44
+ # @param mid [Node] See {Interpolation#mid}
45
+ # @param after [Node] See {Interpolation#after}
46
+ # @param wb [Boolean] See {Interpolation#whitespace_before}
47
+ # @param wa [Boolean] See {Interpolation#whitespace_after}
48
+ # @param originally_text [Boolean] See {Interpolation#originally_text}
49
+ # @param warn_for_color [Boolean] See {Interpolation#warn_for_color}
50
+ def initialize(before, mid, after, wb, wa, opts = {})
51
+ @before = before
52
+ @mid = mid
53
+ @after = after
54
+ @whitespace_before = wb
55
+ @whitespace_after = wa
56
+ @originally_text = opts[:originally_text] || false
57
+ @warn_for_color = opts[:warn_for_color] || false
58
+ @deprecation = opts[:deprecation] || :none
59
+ end
60
+
61
+ # @return [String] A human-readable s-expression representation of the interpolation
62
+ def inspect
63
+ "(interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
64
+ end
65
+
66
+ # @see Node#to_sass
67
+ def to_sass(opts = {})
68
+ return to_quoted_equivalent.to_sass if deprecation == :immediate
69
+
70
+ res = ""
71
+ res << @before.to_sass(opts) if @before
72
+ res << ' ' if @before && @whitespace_before
73
+ res << '#{' unless @originally_text
74
+ res << @mid.to_sass(opts)
75
+ res << '}' unless @originally_text
76
+ res << ' ' if @after && @whitespace_after
77
+ res << @after.to_sass(opts) if @after
78
+ res
79
+ end
80
+
81
+ # Returns an `unquote()` expression that will evaluate to the same value as
82
+ # this interpolation.
83
+ #
84
+ # @return [Sass::Script::Tree::Node]
85
+ def to_quoted_equivalent
86
+ Funcall.new(
87
+ "unquote",
88
+ [to_string_interpolation(self)],
89
+ Sass::Util::NormalizedMap.new,
90
+ nil,
91
+ nil)
92
+ end
93
+
94
+ # Returns the three components of the interpolation, `before`, `mid`, and `after`.
95
+ #
96
+ # @return [Array<Node>]
97
+ # @see #initialize
98
+ # @see Node#children
99
+ def children
100
+ [@before, @mid, @after].compact
101
+ end
102
+
103
+ # @see Node#deep_copy
104
+ def deep_copy
105
+ node = dup
106
+ node.instance_variable_set('@before', @before.deep_copy) if @before
107
+ node.instance_variable_set('@mid', @mid.deep_copy)
108
+ node.instance_variable_set('@after', @after.deep_copy) if @after
109
+ node
110
+ end
111
+
112
+ protected
113
+
114
+ # Converts a script node into a corresponding string interpolation
115
+ # expression.
116
+ #
117
+ # @param node_or_interp [Sass::Script::Tree::Node]
118
+ # @return [Sass::Script::Tree::StringInterpolation]
119
+ def to_string_interpolation(node_or_interp)
120
+ unless node_or_interp.is_a?(Interpolation)
121
+ node = node_or_interp
122
+ return string_literal(node.value.to_s) if node.is_a?(Literal)
123
+ if node.is_a?(StringInterpolation)
124
+ return concat(string_literal(node.quote), concat(node, string_literal(node.quote)))
125
+ end
126
+ return StringInterpolation.new(string_literal(""), node, string_literal(""))
127
+ end
128
+
129
+ interp = node_or_interp
130
+ after_string_or_interp =
131
+ if interp.after
132
+ to_string_interpolation(interp.after)
133
+ else
134
+ string_literal("")
135
+ end
136
+ if interp.after && interp.whitespace_after
137
+ after_string_or_interp = concat(string_literal(' '), after_string_or_interp)
138
+ end
139
+
140
+ mid_string_or_interp = to_string_interpolation(interp.mid)
141
+
142
+ before_string_or_interp =
143
+ if interp.before
144
+ to_string_interpolation(interp.before)
145
+ else
146
+ string_literal("")
147
+ end
148
+ if interp.before && interp.whitespace_before
149
+ before_string_or_interp = concat(before_string_or_interp, string_literal(' '))
150
+ end
151
+
152
+ concat(before_string_or_interp, concat(mid_string_or_interp, after_string_or_interp))
153
+ end
154
+
155
+ private
156
+
157
+ # Evaluates the interpolation.
158
+ #
159
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
160
+ # @return [Sass::Script::Value::String]
161
+ # The SassScript string that is the value of the interpolation
162
+ def _perform(environment)
163
+ res = ""
164
+ res << @before.perform(environment).to_s if @before
165
+ res << " " if @before && @whitespace_before
166
+
167
+ val = @mid.perform(environment)
168
+ if @warn_for_color && val.is_a?(Sass::Script::Value::Color) && val.name
169
+ alternative = Operation.new(Sass::Script::Value::String.new("", :string), @mid, :plus)
170
+ Sass::Util.sass_warn <<MESSAGE
171
+ WARNING on line #{line}, column #{source_range.start_pos.offset}#{" of #{filename}" if filename}:
172
+ You probably don't mean to use the color value `#{val}' in interpolation here.
173
+ It may end up represented as #{val.inspect}, which will likely produce invalid CSS.
174
+ Always quote color names when using them as strings (for example, "#{val}").
175
+ If you really want to use the color value here, use `#{alternative.to_sass}'.
176
+ MESSAGE
177
+ end
178
+
179
+ res << val.to_s(:quote => :none)
180
+ res << " " if @after && @whitespace_after
181
+ res << @after.perform(environment).to_s if @after
182
+ str = Sass::Script::Value::String.new(
183
+ res, :identifier,
184
+ (to_quoted_equivalent.to_sass if deprecation == :potential))
185
+ str.source_range = source_range
186
+ opts(str)
187
+ end
188
+
189
+ # Concatenates two string literals or string interpolation expressions.
190
+ #
191
+ # @param string_or_interp1 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation]
192
+ # @param string_or_interp2 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation]
193
+ # @return [Sass::Script::Tree::StringInterpolation]
194
+ def concat(string_or_interp1, string_or_interp2)
195
+ if string_or_interp1.is_a?(Literal) && string_or_interp2.is_a?(Literal)
196
+ return string_literal(string_or_interp1.value.value + string_or_interp2.value.value)
197
+ end
198
+
199
+ if string_or_interp1.is_a?(Literal)
200
+ string = string_or_interp1
201
+ interp = string_or_interp2
202
+ before = string_literal(string.value.value + interp.before.value.value)
203
+ return StringInterpolation.new(before, interp.mid, interp.after)
204
+ end
205
+
206
+ StringInterpolation.new(
207
+ string_or_interp1.before,
208
+ string_or_interp1.mid,
209
+ concat(string_or_interp1.after, string_or_interp2))
210
+ end
211
+
212
+ # Returns a string literal with the given contents.
213
+ #
214
+ # @param string [String]
215
+ # @return string [Sass::Script::Tree::Literal]
216
+ def string_literal(string)
217
+ Literal.new(Sass::Script::Value::String.new(string, :string))
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,119 @@
1
+ module Sass::Script::Tree
2
+ # A parse tree node representing a list literal. When resolved, this returns a
3
+ # {Sass::Tree::Value::List}.
4
+ class ListLiteral < Node
5
+ # The parse nodes for members of this list.
6
+ #
7
+ # @return [Array<Node>]
8
+ attr_reader :elements
9
+
10
+ # The operator separating the values of the list. Either `:comma`, `:space`, or `:slash`.
11
+ #
12
+ # @return [Symbol]
13
+ attr_reader :separator
14
+
15
+ # Whether the list is surrounded by square brackets.
16
+ #
17
+ # @return [Boolean]
18
+ attr_reader :bracketed
19
+
20
+ # Creates a new list literal.
21
+ #
22
+ # @param elements [Array<Node>] See \{#elements}
23
+ # @param separator [Symbol] See \{#separator}
24
+ # @param bracketed [Boolean] See \{#bracketed}
25
+ def initialize(elements, separator: nil, bracketed: false)
26
+ @elements = elements
27
+ @separator = separator
28
+ @bracketed = bracketed
29
+ end
30
+
31
+ # @see Node#children
32
+ def children; elements; end
33
+
34
+ # @see Value#to_sass
35
+ def to_sass(opts = {})
36
+ return bracketed ? "[]" : "()" if elements.empty?
37
+ members = elements.map do |v|
38
+ if element_needs_parens?(v)
39
+ "(#{v.to_sass(opts)})"
40
+ else
41
+ v.to_sass(opts)
42
+ end
43
+ end
44
+
45
+ if separator == :comma && members.length == 1
46
+ return "#{bracketed ? '[' : '('}#{members.first},#{bracketed ? ']' : ')'}"
47
+ end
48
+
49
+ contents = members.join(sep_str(nil))
50
+ bracketed ? "[#{contents}]" : contents
51
+ end
52
+
53
+ # @see Node#deep_copy
54
+ def deep_copy
55
+ node = dup
56
+ node.instance_variable_set('@elements', elements.map {|e| e.deep_copy})
57
+ node
58
+ end
59
+
60
+ def inspect
61
+ (bracketed ? '[' : '(') +
62
+ elements.map {|e| e.inspect}.join(separator == :space ? ' ' : ', ') +
63
+ (bracketed ? ']' : ')')
64
+ end
65
+
66
+ def force_division!
67
+ # Do nothing. Lists prevent division propagation.
68
+ end
69
+
70
+ protected
71
+
72
+ def _perform(environment)
73
+ list = Sass::Script::Value::List.new(
74
+ elements.map {|e| e.perform(environment)},
75
+ separator: separator,
76
+ bracketed: bracketed)
77
+ list.source_range = source_range
78
+ list.options = options
79
+ list
80
+ end
81
+
82
+ private
83
+
84
+ # Returns whether an element in the list should be wrapped in parentheses
85
+ # when serialized to Sass.
86
+ def element_needs_parens?(element)
87
+ if element.is_a?(ListLiteral)
88
+ return false if element.elements.length < 2
89
+ return false if element.bracketed
90
+ return Sass::Script::Parser.precedence_of(element.separator || :space) <=
91
+ Sass::Script::Parser.precedence_of(separator || :space)
92
+ end
93
+
94
+ return false unless separator == :space
95
+
96
+ if element.is_a?(UnaryOperation)
97
+ return element.operator == :minus || element.operator == :plus
98
+ end
99
+
100
+ return false unless element.is_a?(Operation)
101
+ return true unless element.operator == :div
102
+ !(is_literal_number?(element.operand1) && is_literal_number?(element.operand2))
103
+ end
104
+
105
+ # Returns whether a value is a number literal that shouldn't be divided.
106
+ def is_literal_number?(value)
107
+ value.is_a?(Literal) &&
108
+ value.value.is_a?((Sass::Script::Value::Number)) &&
109
+ !value.value.original.nil?
110
+ end
111
+
112
+ def sep_str(opts = options)
113
+ return ' ' if separator == :space
114
+ return ' / ' if separator == :slash
115
+ return ',' if opts && opts[:style] == :compressed
116
+ ', '
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,49 @@
1
+ module Sass::Script::Tree
2
+ # The parse tree node for a literal scalar value. This wraps an instance of
3
+ # {Sass::Script::Value::Base}.
4
+ #
5
+ # List literals should use {ListLiteral} instead.
6
+ class Literal < Node
7
+ # The wrapped value.
8
+ #
9
+ # @return [Sass::Script::Value::Base]
10
+ attr_reader :value
11
+
12
+ # Creates a new literal value.
13
+ #
14
+ # @param value [Sass::Script::Value::Base]
15
+ # @see #value
16
+ def initialize(value)
17
+ @value = value
18
+ end
19
+
20
+ # @see Node#children
21
+ def children; []; end
22
+
23
+ # @see Node#to_sass
24
+ def to_sass(opts = {}); value.to_sass(opts); end
25
+
26
+ # @see Node#deep_copy
27
+ def deep_copy; dup; end
28
+
29
+ # @see Node#options=
30
+ def options=(options)
31
+ value.options = options
32
+ end
33
+
34
+ def inspect
35
+ value.inspect
36
+ end
37
+
38
+ def force_division!
39
+ value.original = nil if value.is_a?(Sass::Script::Value::Number)
40
+ end
41
+
42
+ protected
43
+
44
+ def _perform(environment)
45
+ value.source_range = source_range
46
+ value
47
+ end
48
+ end
49
+ end