sass 3.1.11 → 3.1.12

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.
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