sass 3.4.0.rc.2 → 3.4.0.rc.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/lib/sass/engine.rb +1 -0
- data/lib/sass/plugin/compiler.rb +136 -28
- data/lib/sass/script/functions.rb +23 -16
- data/lib/sass/script/lexer.rb +37 -15
- data/lib/sass/script/parser.rb +9 -16
- data/lib/sass/script/value/color.rb +6 -8
- data/lib/sass/shared.rb +1 -1
- data/test/sass/compiler_test.rb +12 -3
- data/test/sass/functions_test.rb +26 -41
- data/test/sass/script_test.rb +23 -6
- data/test/sass/source_map_test.rb +19 -0
- data/test/sass/value_helpers_test.rb +2 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 458976233a2583d8c1a41750182734aeef9a2798
|
4
|
+
data.tar.gz: 5e6bd1f3acb111febce42d566914f3b83b1a1964
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73ad9f9ec78111165eca6460bf5e55baf828580ad272671ca4b31ad1cf6c1cf3566243ea034b7437d4b5a54acd34bf4657fdc3b6c5c383f9f2770901fccf4c09
|
7
|
+
data.tar.gz: 38e64c1843b8a98f1bb640764bd2830c5bf607d33630568585a3b838f11c7bba98bd390aef42e705eb6fee1bd9e3d8a25e5eadc0a5e5129561f3e35dc6410131
|
data/MIT-LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2006-
|
1
|
+
Copyright (c) 2006-2014 Hampton Catlin, Natalie Weizenbaum, and Chris Eppstein
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.4.0.rc.
|
1
|
+
3.4.0.rc.3
|
data/VERSION_DATE
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
02 August 2014 01:42:58 UTC
|
data/lib/sass/engine.rb
CHANGED
data/lib/sass/plugin/compiler.rb
CHANGED
@@ -36,19 +36,30 @@ module Sass::Plugin
|
|
36
36
|
options.merge!(opts)
|
37
37
|
end
|
38
38
|
|
39
|
-
# Register a callback to be run
|
39
|
+
# Register a callback to be run before stylesheets are mass-updated.
|
40
40
|
# This is run whenever \{#update\_stylesheets} is called,
|
41
41
|
# unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
|
42
42
|
# is enabled.
|
43
43
|
#
|
44
|
-
# @yield [
|
45
|
-
# @yieldparam
|
46
|
-
# Individual files to be updated
|
47
|
-
# specified in the options.
|
44
|
+
# @yield [files]
|
45
|
+
# @yieldparam files [<(String, String, String)>]
|
46
|
+
# Individual files to be updated. Files in directories specified are included in this list.
|
48
47
|
# The first element of each pair is the source file,
|
49
|
-
# the second is the target CSS file
|
48
|
+
# the second is the target CSS file,
|
49
|
+
# the third is the target sourcemap file.
|
50
50
|
define_callback :updating_stylesheets
|
51
51
|
|
52
|
+
# Register a callback to be run after stylesheets are mass-updated.
|
53
|
+
# This is run whenever \{#update\_stylesheets} is called,
|
54
|
+
# unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
|
55
|
+
# is enabled.
|
56
|
+
#
|
57
|
+
# @yield [updated_files]
|
58
|
+
# @yieldparam updated_files [<(String, String)>]
|
59
|
+
# Individual files that were updated.
|
60
|
+
# The first element of each pair is the source file, the second is the target CSS file.
|
61
|
+
define_callback :updated_stylesheets
|
62
|
+
|
52
63
|
# Register a callback to be run after a single stylesheet is updated.
|
53
64
|
# The callback is only run if the stylesheet is really updated;
|
54
65
|
# if the CSS file is fresh, this won't be run.
|
@@ -67,6 +78,21 @@ module Sass::Plugin
|
|
67
78
|
# The location of the sourcemap being generated, if any.
|
68
79
|
define_callback :updated_stylesheet
|
69
80
|
|
81
|
+
# Register a callback to be run when compilation starts.
|
82
|
+
#
|
83
|
+
# In combination with on_updated_stylesheet, this could be used
|
84
|
+
# to collect compilation statistics like timing or to take a
|
85
|
+
# diff of the changes to the output file.
|
86
|
+
#
|
87
|
+
# @yield [template, css, sourcemap]
|
88
|
+
# @yieldparam template [String]
|
89
|
+
# The location of the Sass/SCSS file being updated.
|
90
|
+
# @yieldparam css [String]
|
91
|
+
# The location of the CSS file being generated.
|
92
|
+
# @yieldparam sourcemap [String]
|
93
|
+
# The location of the sourcemap being generated, if any.
|
94
|
+
define_callback :compilation_starting
|
95
|
+
|
70
96
|
# Register a callback to be run when Sass decides not to update a stylesheet.
|
71
97
|
# In particular, the callback is run when Sass finds that
|
72
98
|
# the template file and none of its dependencies
|
@@ -139,7 +165,8 @@ module Sass::Plugin
|
|
139
165
|
define_callback :template_deleted
|
140
166
|
|
141
167
|
# Register a callback to be run when Sass deletes a CSS file.
|
142
|
-
# This happens when the corresponding Sass/SCSS file has been deleted
|
168
|
+
# This happens when the corresponding Sass/SCSS file has been deleted
|
169
|
+
# and when the compiler cleans the output files.
|
143
170
|
#
|
144
171
|
# @yield [filename]
|
145
172
|
# @yieldparam filename [String]
|
@@ -147,7 +174,8 @@ module Sass::Plugin
|
|
147
174
|
define_callback :deleting_css
|
148
175
|
|
149
176
|
# Register a callback to be run when Sass deletes a sourcemap file.
|
150
|
-
# This happens when the corresponding Sass/SCSS file has been deleted
|
177
|
+
# This happens when the corresponding Sass/SCSS file has been deleted
|
178
|
+
# and when the compiler cleans the output files.
|
151
179
|
#
|
152
180
|
# @yield [filename]
|
153
181
|
# @yieldparam filename [String]
|
@@ -162,35 +190,72 @@ module Sass::Plugin
|
|
162
190
|
# in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
|
163
191
|
# If it has, it updates the CSS file.
|
164
192
|
#
|
165
|
-
# @param individual_files [Array<(String, String)>]
|
193
|
+
# @param individual_files [Array<(String, String[, String])>]
|
166
194
|
# A list of files to check for updates
|
167
195
|
# **in addition to those specified by the
|
168
196
|
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
169
197
|
# The first string in each pair is the location of the Sass/SCSS file,
|
170
198
|
# the second is the location of the CSS file that it should be compiled to.
|
199
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
171
200
|
def update_stylesheets(individual_files = [])
|
172
|
-
individual_files = individual_files.dup
|
173
201
|
Sass::Plugin.checked_for_updates = true
|
174
202
|
staleness_checker = StalenessChecker.new(engine_options)
|
175
203
|
|
176
|
-
|
177
|
-
|
178
|
-
# Get the relative path to the file
|
179
|
-
name = file.sub(template_location.to_s.sub(/\/*$/, '/'), "")
|
180
|
-
css = css_filename(name, css_location)
|
181
|
-
sourcemap = Sass::Util.sourcemap_name(css) if engine_options[:sourcemap] != :none
|
182
|
-
individual_files << [file, css, sourcemap]
|
183
|
-
end
|
184
|
-
end
|
204
|
+
files = file_list(individual_files)
|
205
|
+
run_updating_stylesheets(files)
|
185
206
|
|
186
|
-
|
207
|
+
updated_stylesheets = []
|
208
|
+
files.each do |file, css, sourcemap|
|
187
209
|
# TODO: Does staleness_checker need to check the sourcemap file as well?
|
188
210
|
if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
|
211
|
+
# XXX For consistency, this should return the sourcemap too, but it would
|
212
|
+
# XXX be an API change.
|
213
|
+
updated_stylesheets << [file, css]
|
189
214
|
update_stylesheet(file, css, sourcemap)
|
190
215
|
else
|
191
216
|
run_not_updating_stylesheet(file, css, sourcemap)
|
192
217
|
end
|
193
218
|
end
|
219
|
+
run_updated_stylesheets(updated_stylesheets)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Construct a list of files that might need to be compiled
|
223
|
+
# from the provided individual_files and the template_locations.
|
224
|
+
#
|
225
|
+
# Note: this method does not cache the results as they can change
|
226
|
+
# across invocations when sass files are added or removed.
|
227
|
+
#
|
228
|
+
# @param individual_files [Array<(String, String[, String])>]
|
229
|
+
# A list of files to check for updates
|
230
|
+
# **in addition to those specified by the
|
231
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
232
|
+
# The first string in each pair is the location of the Sass/SCSS file,
|
233
|
+
# the second is the location of the CSS file that it should be compiled to.
|
234
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
235
|
+
# @return [Array<(String, String, String)>]
|
236
|
+
# A list of [sass_file, css_file, sourcemap_file] tuples similar
|
237
|
+
# to what was passed in, but expanded to include the current state
|
238
|
+
# of the directories being updated.
|
239
|
+
def file_list(individual_files = [])
|
240
|
+
files = individual_files.map do |tuple|
|
241
|
+
if tuple.size < 3
|
242
|
+
[tuple[0], tuple[1], Sass::Util.sourcemap_name(tuple[1])]
|
243
|
+
else
|
244
|
+
tuple
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
template_location_array.each do |template_location, css_location|
|
249
|
+
Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
|
250
|
+
# Get the relative path to the file
|
251
|
+
name = Sass::Util.pathname(file).relative_path_from(
|
252
|
+
Sass::Util.pathname(template_location.to_s)).to_s
|
253
|
+
css = css_filename(name, css_location)
|
254
|
+
sourcemap = Sass::Util.sourcemap_name(css) unless engine_options[:sourcemap] == :none
|
255
|
+
files << [file, css, sourcemap]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
files
|
194
259
|
end
|
195
260
|
|
196
261
|
# Watches the template directory (or directories)
|
@@ -211,14 +276,19 @@ module Sass::Plugin
|
|
211
276
|
# The version of Listen distributed with Sass is loaded by default,
|
212
277
|
# but if another version has already been loaded that will be used instead.
|
213
278
|
#
|
214
|
-
# @param individual_files [Array<(String, String)>]
|
215
|
-
# A list of files to
|
279
|
+
# @param individual_files [Array<(String, String[, String])>]
|
280
|
+
# A list of files to check for updates
|
216
281
|
# **in addition to those specified by the
|
217
282
|
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
218
283
|
# The first string in each pair is the location of the Sass/SCSS file,
|
219
284
|
# the second is the location of the CSS file that it should be compiled to.
|
220
|
-
|
221
|
-
|
285
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
286
|
+
# @param options [Hash] The options that control how watching works.
|
287
|
+
# @option options [Boolean] :skip_initial_update
|
288
|
+
# Don't do an initial update when starting the watcher when true
|
289
|
+
def watch(individual_files = [], options = {})
|
290
|
+
options, individual_files = individual_files, [] if individual_files.is_a?(Hash)
|
291
|
+
update_stylesheets(individual_files) unless options[:skip_initial_update]
|
222
292
|
|
223
293
|
directories = watched_paths
|
224
294
|
individual_files.each do |(source, _, _)|
|
@@ -235,7 +305,11 @@ module Sass::Plugin
|
|
235
305
|
|
236
306
|
# TODO: Keep better track of what depends on what
|
237
307
|
# so we don't have to run a global update every time anything changes.
|
238
|
-
|
308
|
+
# XXX The :additional_watch_paths option exists for Compass to use until
|
309
|
+
# a deprecated feature is removed. It may be removed without warning.
|
310
|
+
listener_args = directories +
|
311
|
+
Array(options[:additional_watch_paths]) +
|
312
|
+
[{:relative_paths => false}]
|
239
313
|
|
240
314
|
# The native windows listener is much slower than the polling option, according to
|
241
315
|
# https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e
|
@@ -248,6 +322,7 @@ module Sass::Plugin
|
|
248
322
|
|
249
323
|
listener = create_listener(*listener_args) do |modified, added, removed|
|
250
324
|
on_file_changed(individual_files, modified, added, removed)
|
325
|
+
yield(modified, added, removed) if block_given?
|
251
326
|
end
|
252
327
|
|
253
328
|
if poll && !Sass::Util.listen_geq_2?
|
@@ -267,6 +342,8 @@ module Sass::Plugin
|
|
267
342
|
def engine_options(additional_options = {})
|
268
343
|
opts = options.merge(additional_options)
|
269
344
|
opts[:load_paths] = load_paths(opts)
|
345
|
+
options[:sourcemap] = :auto if options[:sourcemap] == true
|
346
|
+
options[:sourcemap] = :none if options[:sourcemap] == false
|
270
347
|
opts
|
271
348
|
end
|
272
349
|
|
@@ -275,12 +352,42 @@ module Sass::Plugin
|
|
275
352
|
StalenessChecker.stylesheet_needs_update?(css_file, template_file)
|
276
353
|
end
|
277
354
|
|
355
|
+
# Remove all output files that would be created by calling update_stylesheets, if they exist.
|
356
|
+
#
|
357
|
+
# This method runs the deleting_css and deleting_sourcemap callbacks for
|
358
|
+
# the files that are deleted.
|
359
|
+
#
|
360
|
+
# @param individual_files [Array<(String, String[, String])>]
|
361
|
+
# A list of files to check for updates
|
362
|
+
# **in addition to those specified by the
|
363
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
364
|
+
# The first string in each pair is the location of the Sass/SCSS file,
|
365
|
+
# the second is the location of the CSS file that it should be compiled to.
|
366
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
367
|
+
def clean(individual_files = [])
|
368
|
+
file_list(individual_files).each do |(_, css_file, sourcemap_file)|
|
369
|
+
if File.exist?(css_file)
|
370
|
+
run_deleting_css css_file
|
371
|
+
File.delete(css_file)
|
372
|
+
end
|
373
|
+
if sourcemap_file && File.exist?(sourcemap_file)
|
374
|
+
run_deleting_sourcemap sourcemap_file
|
375
|
+
File.delete(sourcemap_file)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
nil
|
379
|
+
end
|
380
|
+
|
278
381
|
private
|
279
382
|
|
280
383
|
def create_listener(*args, &block)
|
281
384
|
Sass::Util.load_listen!
|
282
385
|
if Sass::Util.listen_geq_2?
|
283
|
-
|
386
|
+
# Work around guard/listen#243.
|
387
|
+
options = args.pop if args.last.is_a?(Hash)
|
388
|
+
args.map do |dir|
|
389
|
+
Listen.to(dir, options, &block)
|
390
|
+
end
|
284
391
|
else
|
285
392
|
Listen::Listener.new(*args, &block)
|
286
393
|
end
|
@@ -288,7 +395,7 @@ module Sass::Plugin
|
|
288
395
|
|
289
396
|
def listen_to(listener)
|
290
397
|
if Sass::Util.listen_geq_2?
|
291
|
-
listener.start.join
|
398
|
+
listener.map {|l| l.start}.each {|thread| thread.join}
|
292
399
|
else
|
293
400
|
listener.start!
|
294
401
|
end
|
@@ -328,6 +435,7 @@ module Sass::Plugin
|
|
328
435
|
end
|
329
436
|
|
330
437
|
removed.uniq.each do |f|
|
438
|
+
run_template_deleted(relative_to_pwd(f))
|
331
439
|
if (files = individual_files.find {|(source, _, _)| File.expand_path(source) == f})
|
332
440
|
recompile_required = true
|
333
441
|
# This was a file we were watching explicitly and compiling to a particular location.
|
@@ -347,7 +455,6 @@ module Sass::Plugin
|
|
347
455
|
end
|
348
456
|
end
|
349
457
|
end
|
350
|
-
run_template_deleted(relative_to_pwd(f))
|
351
458
|
end
|
352
459
|
|
353
460
|
if recompile_required
|
@@ -371,6 +478,7 @@ module Sass::Plugin
|
|
371
478
|
:filename => filename,
|
372
479
|
:sourcemap_filename => sourcemap)
|
373
480
|
mapping = nil
|
481
|
+
run_compilation_starting(filename, css, sourcemap)
|
374
482
|
engine = Sass::Engine.for_file(filename, engine_opts)
|
375
483
|
if sourcemap
|
376
484
|
rendered, mapping = engine.render_with_sourcemap(File.basename(sourcemap))
|
@@ -628,10 +628,9 @@ module Sass::Script
|
|
628
628
|
|
629
629
|
color_attrs = [[red, :red], [green, :green], [blue, :blue]].map do |(c, name)|
|
630
630
|
if c.is_unit?("%")
|
631
|
-
|
632
|
-
v * 255 / 100.0
|
631
|
+
c.value * 255 / 100.0
|
633
632
|
elsif c.unitless?
|
634
|
-
|
633
|
+
c.value
|
635
634
|
else
|
636
635
|
raise ArgumentError.new("Expected #{c} to be unitless or have a unit of % but got #{c}")
|
637
636
|
end
|
@@ -683,7 +682,6 @@ module Sass::Script
|
|
683
682
|
assert_type color, :Color, :color
|
684
683
|
assert_type alpha, :Number, :alpha
|
685
684
|
|
686
|
-
Sass::Util.check_range('Alpha channel', 0..1, alpha)
|
687
685
|
color.with(:alpha => alpha.value)
|
688
686
|
when 4
|
689
687
|
red, green, blue, alpha = args
|
@@ -741,11 +739,9 @@ module Sass::Script
|
|
741
739
|
assert_type lightness, :Number, :lightness
|
742
740
|
assert_type alpha, :Number, :alpha
|
743
741
|
|
744
|
-
Sass::Util.check_range('Alpha channel', 0..1, alpha)
|
745
|
-
|
746
742
|
h = hue.value
|
747
|
-
s =
|
748
|
-
l =
|
743
|
+
s = saturation.value
|
744
|
+
l = lightness.value
|
749
745
|
|
750
746
|
# Don't store the string representation for function-created colors, both
|
751
747
|
# because it's not very useful and because some functions aren't supported
|
@@ -1243,12 +1239,27 @@ module Sass::Script
|
|
1243
1239
|
# same time
|
1244
1240
|
def change_color(color, kwargs)
|
1245
1241
|
assert_type color, :Color, :color
|
1246
|
-
with = Sass::Util.
|
1242
|
+
with = Sass::Util.map_hash(
|
1243
|
+
'red' => ['Red value', 0..255],
|
1244
|
+
'green' => ['Green value', 0..255],
|
1245
|
+
'blue' => ['Blue value', 0..255],
|
1246
|
+
'hue' => [],
|
1247
|
+
'saturation' => ['Saturation', 0..100, '%'],
|
1248
|
+
'lightness' => ['Lightness', 0..100, '%'],
|
1249
|
+
'alpha' => ['Alpha channel', 0..1]
|
1250
|
+
) do |name, (desc, range, unit)|
|
1247
1251
|
val = kwargs.delete(name)
|
1248
1252
|
next unless val
|
1249
1253
|
assert_type val, :Number, name
|
1250
|
-
|
1251
|
-
|
1254
|
+
|
1255
|
+
if range
|
1256
|
+
val = Sass::Util.check_range(desc, range, val, unit)
|
1257
|
+
else
|
1258
|
+
val = val.value
|
1259
|
+
end
|
1260
|
+
|
1261
|
+
[name.to_sym, val]
|
1262
|
+
end
|
1252
1263
|
|
1253
1264
|
unless kwargs.empty?
|
1254
1265
|
name, val = kwargs.to_a.first
|
@@ -2609,11 +2620,7 @@ module Sass::Script
|
|
2609
2620
|
assert_type amount, :Number, :amount
|
2610
2621
|
Sass::Util.check_range('Amount', range, amount, units)
|
2611
2622
|
|
2612
|
-
|
2613
|
-
# or should we do so in the Color constructor itself,
|
2614
|
-
# and allow clipping in rgb() et al?
|
2615
|
-
color.with(attr => Sass::Util.restrict(
|
2616
|
-
color.send(attr).send(op, amount.value), range))
|
2623
|
+
color.with(attr => color.send(attr).send(op, amount.value))
|
2617
2624
|
end
|
2618
2625
|
end
|
2619
2626
|
end
|
data/lib/sass/script/lexer.rb
CHANGED
@@ -250,8 +250,14 @@ module Sass
|
|
250
250
|
end
|
251
251
|
|
252
252
|
def token
|
253
|
-
if after_interpolation? && (
|
254
|
-
|
253
|
+
if after_interpolation? && (interp = @interpolation_stack.pop)
|
254
|
+
interp_type, interp_value = interp
|
255
|
+
if interp_type == :special_fun
|
256
|
+
return special_fun_body(interp_value)
|
257
|
+
else
|
258
|
+
raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string
|
259
|
+
return string(interp_value, true)
|
260
|
+
end
|
255
261
|
end
|
256
262
|
|
257
263
|
variable || string(:double, false) || string(:single, false) || number || id || color ||
|
@@ -289,7 +295,7 @@ MESSAGE
|
|
289
295
|
if @scanner[2] == '#{' # '
|
290
296
|
@scanner.pos -= 2 # Don't actually consume the #{
|
291
297
|
@offset -= 2
|
292
|
-
@interpolation_stack << re
|
298
|
+
@interpolation_stack << [:string, re]
|
293
299
|
end
|
294
300
|
str =
|
295
301
|
if re == :uri
|
@@ -368,18 +374,34 @@ MESSAGE
|
|
368
374
|
end
|
369
375
|
|
370
376
|
def special_fun
|
371
|
-
|
372
|
-
return unless
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
377
|
+
prefix = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
|
378
|
+
return unless prefix
|
379
|
+
special_fun_body(1, prefix)
|
380
|
+
end
|
381
|
+
|
382
|
+
def special_fun_body(parens, prefix = nil)
|
383
|
+
str = prefix || ''
|
384
|
+
while (scanned = scan(/.*?([()]|\#{)/m))
|
385
|
+
str << scanned
|
386
|
+
if scanned[-1] == ?(
|
387
|
+
parens += 1
|
388
|
+
next
|
389
|
+
elsif scanned[-1] == ?)
|
390
|
+
parens -= 1
|
391
|
+
next unless parens == 0
|
392
|
+
else
|
393
|
+
raise "[BUG] Unreachable" unless @scanner[1] == '#{' # '
|
394
|
+
str.slice!(-2..-1)
|
395
|
+
@scanner.pos -= 2 # Don't actually consume the #{
|
396
|
+
@offset -= 2
|
397
|
+
@interpolation_stack << [:special_fun, parens]
|
398
|
+
end
|
399
|
+
|
400
|
+
return [:special_fun, Sass::Script::Value::String.new(str)]
|
401
|
+
end
|
402
|
+
|
403
|
+
scan(/.*/)
|
404
|
+
expected!('")"')
|
383
405
|
end
|
384
406
|
|
385
407
|
def special_val
|
data/lib/sass/script/parser.rb
CHANGED
@@ -494,22 +494,14 @@ RUBY
|
|
494
494
|
end
|
495
495
|
|
496
496
|
def special_fun
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
node(
|
506
|
-
Script::Tree::Interpolation.new(
|
507
|
-
l, i,
|
508
|
-
r && literal_node(Script::Value::String.new(r),
|
509
|
-
i.source_range.end_pos, end_pos),
|
510
|
-
false, false),
|
511
|
-
start_pos, end_pos)
|
512
|
-
end
|
497
|
+
first = try_tok(:special_fun)
|
498
|
+
return paren unless first
|
499
|
+
str = literal_node(first.value, first.source_range)
|
500
|
+
return str unless try_tok(:begin_interpolation)
|
501
|
+
mid = parse_interpolated
|
502
|
+
last = assert_expr(:special_fun)
|
503
|
+
node(Tree::Interpolation.new(str, mid, last, false, false),
|
504
|
+
first.source_range.start_pos)
|
513
505
|
end
|
514
506
|
|
515
507
|
def paren
|
@@ -571,6 +563,7 @@ RUBY
|
|
571
563
|
:mixin_arglist => "mixin argument",
|
572
564
|
:fn_arglist => "function argument",
|
573
565
|
:splat => "...",
|
566
|
+
:special_fun => '")"',
|
574
567
|
}
|
575
568
|
|
576
569
|
def assert_expr(name, expected = nil)
|
@@ -196,9 +196,9 @@ module Sass::Script::Value
|
|
196
196
|
# Constructs an RGB or HSL color object,
|
197
197
|
# optionally with an alpha channel.
|
198
198
|
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
# The alpha value
|
199
|
+
# RGB values are clipped within 0 and 255.
|
200
|
+
# Saturation and lightness values are clipped within 0 and 100.
|
201
|
+
# The alpha value is clipped within 0 and 1.
|
202
202
|
#
|
203
203
|
# @raise [Sass::SyntaxError] if any color value isn't in the specified range
|
204
204
|
#
|
@@ -258,17 +258,15 @@ module Sass::Script::Value
|
|
258
258
|
|
259
259
|
[:red, :green, :blue].each do |k|
|
260
260
|
next if @attrs[k].nil?
|
261
|
-
@attrs[k] = @attrs[k].to_i
|
262
|
-
Sass::Util.check_range("#{k.to_s.capitalize} value", 0..255, @attrs[k])
|
261
|
+
@attrs[k] = Sass::Util.restrict(@attrs[k].to_i, 0..255)
|
263
262
|
end
|
264
263
|
|
265
264
|
[:saturation, :lightness].each do |k|
|
266
265
|
next if @attrs[k].nil?
|
267
|
-
|
268
|
-
@attrs[k] = Sass::Util.check_range("#{k.to_s.capitalize}", 0..100, value, '%')
|
266
|
+
@attrs[k] = Sass::Util.restrict(@attrs[k], 0..100)
|
269
267
|
end
|
270
268
|
|
271
|
-
@attrs[:alpha] = Sass::Util.
|
269
|
+
@attrs[:alpha] = Sass::Util.restrict(@attrs[:alpha], 0..1)
|
272
270
|
end
|
273
271
|
|
274
272
|
# Create a new color from a valid CSS hex string.
|
data/lib/sass/shared.rb
CHANGED
data/test/sass/compiler_test.rb
CHANGED
@@ -82,9 +82,18 @@ class CompilerTest < MiniTest::Test
|
|
82
82
|
|
83
83
|
private
|
84
84
|
def create_listener(*args, &on_filesystem_event)
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
if Sass::Util.listen_geq_2?
|
86
|
+
options = args.pop if args.last.is_a?(Hash)
|
87
|
+
args.map do |dir|
|
88
|
+
@fake_listener = FakeListener.new(*args, &on_filesystem_event)
|
89
|
+
@fake_listener.on_start!(&run_during_start)
|
90
|
+
@fake_listener
|
91
|
+
end
|
92
|
+
else
|
93
|
+
@fake_listener = FakeListener.new(*args, &on_filesystem_event)
|
94
|
+
@fake_listener.on_start!(&run_during_start)
|
95
|
+
@fake_listener
|
96
|
+
end
|
88
97
|
end
|
89
98
|
end
|
90
99
|
|
data/test/sass/functions_test.rb
CHANGED
@@ -90,9 +90,9 @@ class SassFunctionTest < MiniTest::Test
|
|
90
90
|
assert_equal "#33cccc", evaluate("hsl($hue: 180, $saturation: 60%, $lightness: 50%)")
|
91
91
|
end
|
92
92
|
|
93
|
-
def
|
94
|
-
|
95
|
-
|
93
|
+
def test_hsl_clamps_bounds
|
94
|
+
assert_equal("#1f1f1f", evaluate("hsl(10, -114, 12)"))
|
95
|
+
assert_equal("white", evaluate("hsl(10, 10, 256%)"))
|
96
96
|
end
|
97
97
|
|
98
98
|
def test_hsl_checks_types
|
@@ -108,11 +108,11 @@ class SassFunctionTest < MiniTest::Test
|
|
108
108
|
assert_equal "rgba(51, 204, 204, 0.4)", evaluate("hsla($hue: 180, $saturation: 60%, $lightness: 50%, $alpha: 0.4)")
|
109
109
|
end
|
110
110
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
111
|
+
def test_hsla_clamps_bounds
|
112
|
+
assert_equal("#1f1f1f", evaluate("hsla(10, -114, 12, 1)"))
|
113
|
+
assert_equal("rgba(255, 255, 255, 0)", evaluate("hsla(10, 10, 256%, 0)"))
|
114
|
+
assert_equal("rgba(28, 24, 23, 0)", evaluate("hsla(10, 10, 10, -0.1)"))
|
115
|
+
assert_equal("#1c1817", evaluate("hsla(10, 10, 10, 1.1)"))
|
116
116
|
end
|
117
117
|
|
118
118
|
def test_hsla_checks_types
|
@@ -212,26 +212,18 @@ class SassFunctionTest < MiniTest::Test
|
|
212
212
|
assert_equal("springgreen", evaluate("rgb(0%, 100%, 50%)"))
|
213
213
|
end
|
214
214
|
|
215
|
-
def
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
"rgb(1, 1, 256)")
|
222
|
-
assert_error_message("$green: Color value 256 must be between 0 and 255 for `rgb'",
|
223
|
-
"rgb(1, 256, 257)")
|
224
|
-
assert_error_message("$red: Color value -1 must be between 0 and 255 for `rgb'",
|
225
|
-
"rgb(-1, 1, 1)")
|
215
|
+
def test_rgb_clamps_bounds
|
216
|
+
assert_equal("#ff0101", evaluate("rgb(256, 1, 1)"))
|
217
|
+
assert_equal("#01ff01", evaluate("rgb(1, 256, 1)"))
|
218
|
+
assert_equal("#0101ff", evaluate("rgb(1, 1, 256)"))
|
219
|
+
assert_equal("#01ffff", evaluate("rgb(1, 256, 257)"))
|
220
|
+
assert_equal("#000101", evaluate("rgb(-1, 1, 1)"))
|
226
221
|
end
|
227
222
|
|
228
|
-
def
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
"rgb(0, -0.1%, 0)")
|
233
|
-
assert_error_message("$blue: Color value 101% must be between 0% and 100% for `rgb'",
|
234
|
-
"rgb(0, 0, 101%)")
|
223
|
+
def test_rgb_clamps_percent_bounds
|
224
|
+
assert_equal("red", evaluate("rgb(100.1%, 0, 0)"))
|
225
|
+
assert_equal("black", evaluate("rgb(0, -0.1%, 0)"))
|
226
|
+
assert_equal("blue", evaluate("rgb(0, 0, 101%)"))
|
235
227
|
end
|
236
228
|
|
237
229
|
def test_rgb_tests_types
|
@@ -247,21 +239,14 @@ class SassFunctionTest < MiniTest::Test
|
|
247
239
|
assert_equal("rgba(0, 255, 127, 0)", evaluate("rgba($red: 0, $green: 255, $blue: 127, $alpha: 0)"))
|
248
240
|
end
|
249
241
|
|
250
|
-
def
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
"rgba(1, 256, 257, 0.3)")
|
259
|
-
assert_error_message("$red: Color value -1 must be between 0 and 255 for `rgba'",
|
260
|
-
"rgba(-1, 1, 1, 0.3)")
|
261
|
-
assert_error_message("Alpha channel -0.2 must be between 0 and 1 for `rgba'",
|
262
|
-
"rgba(1, 1, 1, -0.2)")
|
263
|
-
assert_error_message("Alpha channel 1.2 must be between 0 and 1 for `rgba'",
|
264
|
-
"rgba(1, 1, 1, 1.2)")
|
242
|
+
def test_rgba_clamps_bounds
|
243
|
+
assert_equal("rgba(255, 1, 1, 0.3)", evaluate("rgba(256, 1, 1, 0.3)"))
|
244
|
+
assert_equal("rgba(1, 255, 1, 0.3)", evaluate("rgba(1, 256, 1, 0.3)"))
|
245
|
+
assert_equal("rgba(1, 1, 255, 0.3)", evaluate("rgba(1, 1, 256, 0.3)"))
|
246
|
+
assert_equal("rgba(1, 255, 255, 0.3)", evaluate("rgba(1, 256, 257, 0.3)"))
|
247
|
+
assert_equal("rgba(0, 1, 1, 0.3)", evaluate("rgba(-1, 1, 1, 0.3)"))
|
248
|
+
assert_equal("rgba(1, 1, 1, 0)", evaluate("rgba(1, 1, 1, -0.2)"))
|
249
|
+
assert_equal("#010101", evaluate("rgba(1, 1, 1, 1.2)"))
|
265
250
|
end
|
266
251
|
|
267
252
|
def test_rgba_tests_types
|
data/test/sass/script_test.rb
CHANGED
@@ -21,14 +21,14 @@ end
|
|
21
21
|
class SassScriptTest < MiniTest::Test
|
22
22
|
include Sass::Script
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
24
|
+
def test_color_clamps_input
|
25
|
+
assert_equal 0, Sass::Script::Value::Color.new([1, 2, -1]).blue
|
26
|
+
assert_equal 255, Sass::Script::Value::Color.new([256, 2, 3]).red
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
29
|
+
def test_color_clamps_rgba_input
|
30
|
+
assert_equal 1, Sass::Script::Value::Color.new([1, 2, 3, 1.1]).alpha
|
31
|
+
assert_equal 0, Sass::Script::Value::Color.new([1, 2, 3, -0.1]).alpha
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_string_escapes
|
@@ -838,6 +838,23 @@ SCSS
|
|
838
838
|
assert_equal 'foobar', resolve("'foo\\\nbar'")
|
839
839
|
end
|
840
840
|
|
841
|
+
def test_unclosed_special_fun
|
842
|
+
assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "calc(foo()": expected ")", was ""') do
|
843
|
+
resolve("calc(foo()")
|
844
|
+
end
|
845
|
+
assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "calc(#{\')\'}": expected ")", was ""') do
|
846
|
+
resolve("calc(\#{')'}")
|
847
|
+
end
|
848
|
+
assert_raise_message(Sass::SyntaxError, 'Invalid CSS after "calc(#{foo": expected "}", was ""') do
|
849
|
+
resolve("calc(\#{foo")
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
def test_special_fun_with_interpolation
|
854
|
+
assert_equal "calc())", resolve("calc(\#{')'})")
|
855
|
+
assert_equal "calc(# {foo})", resolve("calc(# {foo})")
|
856
|
+
end
|
857
|
+
|
841
858
|
# Regression Tests
|
842
859
|
|
843
860
|
def test_repeatedly_modified_color
|
@@ -790,6 +790,25 @@ SCSS
|
|
790
790
|
CSS
|
791
791
|
end
|
792
792
|
|
793
|
+
def test_multiline_interpolation_source_range
|
794
|
+
engine = Sass::Engine.new(<<-SCSS, cache: false, syntax: :scss)
|
795
|
+
p {
|
796
|
+
filter: progid:DXImageTransform(
|
797
|
+
'\#{123}');
|
798
|
+
}
|
799
|
+
SCSS
|
800
|
+
|
801
|
+
interpolated = engine.to_tree.children.
|
802
|
+
first.children.
|
803
|
+
first.value.children[1]
|
804
|
+
assert_equal interpolated.to_sass, "\#{123}"
|
805
|
+
range = interpolated.source_range
|
806
|
+
assert_equal 3, range.start_pos.line
|
807
|
+
assert_equal 12, range.start_pos.offset
|
808
|
+
assert_equal 3, range.end_pos.line
|
809
|
+
assert_equal 18, range.end_pos.offset
|
810
|
+
end
|
811
|
+
|
793
812
|
def test_sources_array_is_uri_escaped
|
794
813
|
map = Sass::Source::Map.new
|
795
814
|
importer = Sass::Importers::Filesystem.new('.')
|
@@ -51,10 +51,8 @@ class ValueHelpersTest < MiniTest::Test
|
|
51
51
|
assert_equal 0.5, color_with_alpha.alpha
|
52
52
|
end
|
53
53
|
|
54
|
-
def
|
55
|
-
|
56
|
-
hex_color("FF007F", 50)
|
57
|
-
end
|
54
|
+
def test_hex_color_alpha_clamps_0_to_1
|
55
|
+
assert_equal 1, hex_color("FF007F", 50).alpha
|
58
56
|
end
|
59
57
|
|
60
58
|
def test_hsl_color_without_alpha
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sass
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.4.0.rc.
|
4
|
+
version: 3.4.0.rc.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Natalie Weizenbaum
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-
|
13
|
+
date: 2014-08-02 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: yard
|