sass 3.1.11 → 3.1.12

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.11
1
+ 3.1.12
@@ -683,7 +683,7 @@ WARNING
683
683
  :line => @line + 1) unless line.children.empty?
684
684
  Tree::CharsetNode.new(name)
685
685
  elsif directive == "media"
686
- Tree::MediaNode.new(value)
686
+ Tree::MediaNode.new(value.split(',').map {|s| s.strip})
687
687
  else
688
688
  Tree::DirectiveNode.new(line.text)
689
689
  end
@@ -26,7 +26,7 @@ module Sass
26
26
  unless parent
27
27
  @stack = []
28
28
  @mixins_in_use = Set.new
29
- set_var("important", Script::String.new("!important"))
29
+ @files_in_use = Set.new
30
30
  end
31
31
  end
32
32
 
@@ -59,7 +59,8 @@ module Sass
59
59
  else
60
60
  stack.push(top_of_stack = frame_info)
61
61
  end
62
- mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin] && !top_of_stack[:prepared]
62
+ mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin]
63
+ files_in_use << top_of_stack[:filename] if top_of_stack[:filename]
63
64
  end
64
65
 
65
66
  # Like \{#push\_frame}, but next time a stack frame is pushed,
@@ -72,9 +73,8 @@ module Sass
72
73
 
73
74
  # Pop a stack frame from the mixin/include stack.
74
75
  def pop_frame
75
- stack.pop if stack.last && stack.last[:prepared]
76
- popped = stack.pop
77
- mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
76
+ pop_and_unuse if stack.last && stack.last[:prepared]
77
+ pop_and_unuse
78
78
  end
79
79
 
80
80
  # A list of stack frames in the mixin/include stack.
@@ -93,6 +93,13 @@ module Sass
93
93
  @mixins_in_use ||= @parent.mixins_in_use
94
94
  end
95
95
 
96
+ # A set of names of files currently present in the stack.
97
+ #
98
+ # @return [Set<String>] The filenames.
99
+ def files_in_use
100
+ @files_in_use ||= @parent.files_in_use
101
+ end
102
+
96
103
  def stack_trace
97
104
  trace = []
98
105
  stack.reverse.each_with_index do |entry, i|
@@ -106,6 +113,13 @@ module Sass
106
113
 
107
114
  private
108
115
 
116
+ def pop_and_unuse
117
+ popped = stack.pop
118
+ mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
119
+ files_in_use.delete(popped[:filename]) if popped && popped[:filename]
120
+ popped
121
+ end
122
+
109
123
  def parent_options
110
124
  @parent_options ||= @parent && @parent.options
111
125
  end
@@ -389,7 +389,7 @@ MSG
389
389
  dirs.map! {|from, to| [from, to || from]}
390
390
  ::Sass::Plugin.options[:template_location] = dirs
391
391
 
392
- ::Sass::Plugin.on_updating_stylesheet do |_, css|
392
+ ::Sass::Plugin.on_updated_stylesheet do |_, css|
393
393
  if File.exists? css
394
394
  puts_action :overwrite, :yellow, css
395
395
  else
@@ -15,7 +15,6 @@ module Sass
15
15
  # Starts the read-eval-print loop.
16
16
  def run
17
17
  environment = Environment.new
18
- environment.set_var('important', Script::String.new('!important'))
19
18
  @line = 0
20
19
  loop do
21
20
  @line += 1
@@ -968,7 +968,7 @@ module Sass::Script
968
968
  #
969
969
  # Finally, the weight of color1 is renormalized to be within [0, 1]
970
970
  # and the weight of color2 is given by 1 minus the weight of color1.
971
- p = weight.value/100.0
971
+ p = (weight.value/100.0).to_f
972
972
  w = p*2 - 1
973
973
  a = color1.alpha - color2.alpha
974
974
 
@@ -360,7 +360,7 @@ module Sass::Script
360
360
  elsif num % 1 == 0.0
361
361
  num.to_i
362
362
  else
363
- (num * self.precision_factor).round / self.precision_factor
363
+ ((num * self.precision_factor).round / self.precision_factor).to_f
364
364
  end
365
365
  end
366
366
 
@@ -21,7 +21,6 @@ module Sass
21
21
 
22
22
  # @return [String] A string representation of the variable
23
23
  def inspect(opts = {})
24
- return "!important" if name == "important"
25
24
  "$#{dasherize(name, opts)}"
26
25
  end
27
26
  alias_method :to_sass, :inspect
@@ -287,20 +287,23 @@ module Sass
287
287
  def use_css_import?; false; end
288
288
 
289
289
  def media_directive
290
- val = str {media_query_list}.strip
291
- block(node(Sass::Tree::MediaNode.new(val)), :directive)
290
+ block(node(Sass::Tree::MediaNode.new(media_query_list)), :directive)
292
291
  end
293
292
 
294
293
  # http://www.w3.org/TR/css3-mediaqueries/#syntax
295
294
  def media_query_list
296
- return unless media_query
295
+ has_q = false
296
+ q = str {has_q = media_query}
297
+
298
+ return unless has_q
299
+ queries = [q.strip]
297
300
 
298
301
  ss
299
302
  while tok(/,/)
300
- ss; expr!(:media_query); ss
303
+ ss; queries << str {expr!(:media_query)}.strip; ss
301
304
  end
302
305
 
303
- true
306
+ queries
304
307
  end
305
308
 
306
309
  def media_query
@@ -437,7 +440,7 @@ module Sass
437
440
  end
438
441
 
439
442
  def selector_sequence
440
- if sel = tok(STATIC_SELECTOR)
443
+ if sel = tok(STATIC_SELECTOR, true)
441
444
  return [sel]
442
445
  end
443
446
 
@@ -681,7 +684,7 @@ MESSAGE
681
684
  # we don't parse it at all, and instead return a plain old string
682
685
  # containing the value.
683
686
  # This results in a dramatic speed increase.
684
- if val = tok(STATIC_VALUE)
687
+ if val = tok(STATIC_VALUE, true)
685
688
  return space, Sass::Script::String.new(val.strip)
686
689
  end
687
690
  return space, sass_script(:parse)
@@ -770,7 +773,7 @@ MESSAGE
770
773
  end
771
774
 
772
775
  def interp_ident(start = IDENT)
773
- return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP)
776
+ return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
774
777
  res = [val]
775
778
  while val = tok(NAME) || interpolation
776
779
  res << val
@@ -944,9 +947,20 @@ MESSAGE
944
947
  # This is important because `#tok` is called all the time.
945
948
  NEWLINE = "\n"
946
949
 
947
- def tok(rx)
950
+ def tok(rx, last_group_lookahead = false)
948
951
  res = @scanner.scan(rx)
949
952
  if res
953
+ # This fixes https://github.com/nex3/sass/issues/104, which affects
954
+ # Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
955
+ # positive lookahead operator in the Regexp (which matches without
956
+ # consuming the matched group), with a match that does consume the
957
+ # group, but then rewinds the scanner and removes the group from the
958
+ # end of the matched string. This fix makes the assumption that the
959
+ # matched group will always occur at the end of the match.
960
+ if last_group_lookahead && @scanner[-1]
961
+ @scanner.pos -= @scanner[-1].length
962
+ res.slice!(-@scanner[-1].length..-1)
963
+ end
950
964
  @line += res.count(NEWLINE)
951
965
  @expected = nil
952
966
  if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
@@ -112,7 +112,7 @@ module Sass
112
112
  INTERP_START = /#\{/
113
113
  MOZ_ANY = quote(":-moz-any(", Regexp::IGNORECASE)
114
114
 
115
- IDENT_HYPHEN_INTERP = /-(?=#\{)/
115
+ IDENT_HYPHEN_INTERP = /-(#\{)/
116
116
  STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\"/
117
117
  STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\'/
118
118
  STRING_NOINTERP = /#{STRING1_NOINTERP}|#{STRING2_NOINTERP}/
@@ -120,9 +120,13 @@ module Sass
120
120
  # We could use it for 1.9 only, but I don't want to introduce a cross-version
121
121
  # behavior difference.
122
122
  # In any case, almost all CSS idents will be matched by this.
123
- STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|\s(?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important)+(?=[;}])/i
124
-
125
- STATIC_SELECTOR = /(#{NMCHAR}|\s|[,>+*]|[:#.]#{NMSTART})+(?=[{])/i
123
+ #
124
+ # We explicitly avoid parsing newlines or values/selectors longer than
125
+ # about 50 characters. This mitigates the problem of exponential parsing
126
+ # time when a value has a long string of valid, parsable content followed
127
+ # by something invalid.
128
+ STATIC_VALUE = /(-?#{NMSTART}|#{STRING_NOINTERP}|[ \t](?!%)|#[a-f0-9]|[,%]|#{NUM}|\!important){0,50}([;}])/i
129
+ STATIC_SELECTOR = /(#{NMCHAR}|[ \t]|[,>+*]|[:#.]#{NMSTART}){0,50}([{])/i
126
130
  end
127
131
  end
128
132
  end
@@ -6,9 +6,9 @@ module Sass::Tree
6
6
  #
7
7
  # @see Sass::Tree
8
8
  class MediaNode < DirectiveNode
9
- # The media query (e.g. `print` or `screen`).
9
+ # The media query. A list of comma-separated queries (e.g. `print` or `screen`).
10
10
  #
11
- # @return [String]
11
+ # @return [Array<String>]
12
12
  attr_accessor :query
13
13
 
14
14
  # @see RuleNode#tabs
@@ -17,7 +17,7 @@ module Sass::Tree
17
17
  # @see RuleNode#group_end
18
18
  attr_accessor :group_end
19
19
 
20
- # @param query [String] See \{#query}
20
+ # @param query [Array<String>] See \{#query}
21
21
  def initialize(query)
22
22
  @query = query
23
23
  @tabs = 0
@@ -26,7 +26,7 @@ module Sass::Tree
26
26
 
27
27
  # @see DirectiveNode#value
28
28
  def value
29
- "@media #{query}"
29
+ "@media #{query.join(', ')}"
30
30
  end
31
31
  end
32
32
  end
@@ -154,7 +154,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
154
154
  end
155
155
 
156
156
  def visit_media(node)
157
- "#{tab_str}@media #{node.query}#{yield}"
157
+ "#{tab_str}@media #{node.query.join(', ')}#{yield}"
158
158
  end
159
159
 
160
160
  def visit_mixindef(node)
@@ -230,7 +230,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
230
230
  def selector_to_sass(sel)
231
231
  sel.map do |r|
232
232
  if r.is_a?(String)
233
- r.gsub(/(,[ \t]*)?\n\s*/) {$1 ? $1 + "\n" : " "}
233
+ r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "}
234
234
  else
235
235
  "\#{#{r.to_sass(@options)}}"
236
236
  end
@@ -239,7 +239,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
239
239
 
240
240
  def selector_to_scss(sel)
241
241
  sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(@options)}}"}.
242
- join.gsub(/^[ \t]*/, tab_str)
242
+ join.gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '')
243
243
  end
244
244
 
245
245
  def semi
@@ -117,7 +117,9 @@ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
117
117
 
118
118
  media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
119
119
  node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
120
- media.each {|n| n.query = "#{node.query} and #{n.query}"}
120
+ media.each do |n|
121
+ n.query = node.query.map {|pq| n.query.map {|cq| "#{pq} and #{cq}"}}.flatten
122
+ end
121
123
  (node.children.empty? ? [] : [node]) + media
122
124
  end
123
125
 
@@ -137,9 +137,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
137
137
  if path = node.css_import?
138
138
  return Sass::Tree::DirectiveNode.new("@import url(#{path})")
139
139
  end
140
+ file = node.imported_file
141
+ handle_import_loop!(node) if @environment.files_in_use.include?(file.options[:filename])
140
142
 
141
143
  @environment.push_frame(:filename => node.filename, :line => node.line)
142
- root = node.imported_file.to_tree
144
+ root = file.to_tree
143
145
  Sass::Tree::Visitors::CheckNesting.visit(root)
144
146
  node.children = root.children.map {|c| visit(c)}.flatten
145
147
  node
@@ -293,9 +295,7 @@ END
293
295
  def handle_include_loop!(node)
294
296
  msg = "An @include loop has been found:"
295
297
  mixins = @environment.stack.map {|s| s[:mixin]}.compact
296
- if mixins.size == 2 && mixins[0] == mixins[1]
297
- raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself")
298
- end
298
+ raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
299
299
 
300
300
  mixins << node.name
301
301
  msg << "\n" << Sass::Util.enum_cons(mixins, 2).map do |m1, m2|
@@ -304,6 +304,18 @@ END
304
304
  raise Sass::SyntaxError.new(msg)
305
305
  end
306
306
 
307
+ def handle_import_loop!(node)
308
+ msg = "An @import loop has been found:"
309
+ files = @environment.stack.map {|s| s[:filename]}.compact
310
+ raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself") if files.size == 1
311
+
312
+ files << node.filename << node.imported_file.options[:filename]
313
+ msg << "\n" << Sass::Util.enum_cons(files, 2).map do |m1, m2|
314
+ " #{m1} imports #{m2}"
315
+ end.join("\n")
316
+ raise Sass::SyntaxError.new(msg)
317
+ end
318
+
307
319
  def check_for_loud_silent_comment(node)
308
320
  return unless node.loud && node.silent
309
321
  Sass::Util.sass_warn <<MESSAGE
@@ -180,7 +180,7 @@ SCSS
180
180
  def test_multiline_properties
181
181
  assert_scss_to_sass <<SASS, <<SCSS
182
182
  foo bar
183
- baz: bip bam boon
183
+ baz: bip bam boon
184
184
  SASS
185
185
  foo bar {
186
186
  baz:
@@ -191,7 +191,7 @@ SCSS
191
191
 
192
192
  assert_scss_to_scss <<OUT, <<IN
193
193
  foo bar {
194
- baz: bip bam boon; }
194
+ baz: bip bam boon; }
195
195
  OUT
196
196
  foo bar {
197
197
  baz:
@@ -470,6 +470,53 @@ MESSAGE
470
470
  assert_hash_has(err.sass_backtrace[2], :mixin => "foo", :line => 2)
471
471
  end
472
472
 
473
+ def test_basic_import_loop_exception
474
+ import = filename_for_test
475
+ importer = MockImporter.new
476
+ importer.add_import(import, "@import '#{import}'")
477
+
478
+ engine = Sass::Engine.new("@import '#{import}'", :filename => import,
479
+ :load_paths => [importer])
480
+
481
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
482
+ An @import loop has been found: #{import} imports itself
483
+ ERR
484
+ end
485
+
486
+ def test_double_import_loop_exception
487
+ importer = MockImporter.new
488
+ importer.add_import("foo", "@import 'bar'")
489
+ importer.add_import("bar", "@import 'foo'")
490
+
491
+ engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
492
+ :load_paths => [importer])
493
+
494
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
495
+ An @import loop has been found:
496
+ #{filename_for_test} imports foo
497
+ foo imports bar
498
+ bar imports foo
499
+ ERR
500
+ end
501
+
502
+ def test_deep_import_loop_exception
503
+ importer = MockImporter.new
504
+ importer.add_import("foo", "@import 'bar'")
505
+ importer.add_import("bar", "@import 'baz'")
506
+ importer.add_import("baz", "@import 'foo'")
507
+
508
+ engine = Sass::Engine.new('@import "foo"', :filename => filename_for_test,
509
+ :load_paths => [importer])
510
+
511
+ assert_raise_message(Sass::SyntaxError, <<ERR.rstrip) {engine.render}
512
+ An @import loop has been found:
513
+ #{filename_for_test} imports foo
514
+ foo imports bar
515
+ bar imports baz
516
+ baz imports foo
517
+ ERR
518
+ end
519
+
473
520
  def test_exception_css_with_offset
474
521
  opts = {:full_exception => true, :line => 362}
475
522
  render(("a\n b: c\n" * 10) + "d\n e:\n" + ("f\n g: h\n" * 10), opts)
@@ -1979,6 +2026,19 @@ CSS
1979
2026
  SASS
1980
2027
  end
1981
2028
 
2029
+ def test_double_media_bubbling_with_commas
2030
+ assert_equal <<CSS, render(<<SASS)
2031
+ @media foo and baz, foo and bang, bar and baz, bar and bang {
2032
+ .foo {
2033
+ c: d; } }
2034
+ CSS
2035
+ @media foo, bar
2036
+ @media baz, bang
2037
+ .foo
2038
+ c: d
2039
+ SASS
2040
+ end
2041
+
1982
2042
  def test_rule_media_rule_bubbling
1983
2043
  assert_equal <<CSS, render(<<SASS)
1984
2044
  @media bar {
@@ -2041,6 +2101,24 @@ CSS
2041
2101
 
2042
2102
  # Regression tests
2043
2103
 
2104
+ def test_tricky_mixin_loop_exception
2105
+ render <<SASS
2106
+ @mixin foo($a)
2107
+ @if $a
2108
+ @include foo(false)
2109
+ @include foo(true)
2110
+ @else
2111
+ a: b
2112
+
2113
+ a
2114
+ @include foo(true)
2115
+ SASS
2116
+ assert(false, "Exception not raised")
2117
+ rescue Sass::SyntaxError => err
2118
+ assert_equal("An @include loop has been found: foo includes itself", err.message)
2119
+ assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 3)
2120
+ end
2121
+
2044
2122
  def test_interpolated_comment_in_mixin
2045
2123
  assert_equal <<CSS, render(<<SASS)
2046
2124
  /* color: red */
@@ -1270,4 +1270,29 @@ CSS
1270
1270
  foo {color: darken(black, 10%)}
1271
1271
  SCSS
1272
1272
  end
1273
+
1274
+ # ref: https://github.com/nex3/sass/issues/104
1275
+ def test_no_buffer_overflow
1276
+ template = render <<SCSS
1277
+ .aaa {
1278
+ background-color: white;
1279
+ }
1280
+ .aaa .aaa .aaa {
1281
+ background-color: black;
1282
+ }
1283
+ .bbb {
1284
+ @extend .aaa;
1285
+ }
1286
+ .xxx {
1287
+ @extend .bbb;
1288
+ }
1289
+ .yyy {
1290
+ @extend .bbb;
1291
+ }
1292
+ .zzz {
1293
+ @extend .bbb;
1294
+ }
1295
+ SCSS
1296
+ Sass::SCSS::Parser.new(template, "test.scss").parse
1297
+ end
1273
1298
  end
@@ -4,6 +4,7 @@ require 'test/unit'
4
4
  require 'fileutils'
5
5
  $:.unshift lib_dir unless $:.include?(lib_dir)
6
6
  require 'sass'
7
+ require 'mathn' if ENV['MATHN'] == 'true'
7
8
 
8
9
  Sass::RAILS_LOADED = true unless defined?(Sass::RAILS_LOADED)
9
10
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sass
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 3
8
8
  - 1
9
- - 11
10
- version: 3.1.11
9
+ - 12
10
+ version: 3.1.12
11
11
  platform: ruby
12
12
  authors:
13
13
  - Nathan Weizenbaum
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2011-11-28 00:00:00 -08:00
20
+ date: 2011-12-16 00:00:00 -08:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency