sass 3.1.8 → 3.1.9

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.8
1
+ 3.1.9
@@ -90,7 +90,10 @@ module Sass
90
90
  #
91
91
  # `children`: `Array<Line>`
92
92
  # : The lines nested below this one.
93
- class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children)
93
+ #
94
+ # `comment_tab_str`: `String?`
95
+ # : The prefix indentation for this comment, if it is a comment.
96
+ class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children, :comment_tab_str)
94
97
  def comment?
95
98
  text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
96
99
  end
@@ -107,6 +110,10 @@ module Sass
107
110
  # which is not output as a CSS comment.
108
111
  SASS_COMMENT_CHAR = ?/
109
112
 
113
+ # The character that indicates that a comment allows interpolation
114
+ # and should be preserved even in `:compressed` mode.
115
+ SASS_LOUD_COMMENT_CHAR = ?!
116
+
110
117
  # The character that follows the general COMMENT_CHAR and designates a CSS comment,
111
118
  # which is embedded in the CSS document.
112
119
  CSS_COMMENT_CHAR = ?*
@@ -420,7 +427,8 @@ but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
420
427
  MSG
421
428
  end
422
429
 
423
- last.text << "\n" << $1
430
+ last.comment_tab_str ||= comment_tab_str
431
+ last.text << "\n" << line
424
432
  true
425
433
  end
426
434
 
@@ -486,8 +494,8 @@ MSG
486
494
  if child.is_a?(Tree::CommentNode) && child.silent
487
495
  if continued_comment &&
488
496
  child.line == continued_comment.line +
489
- continued_comment.value.count("\n") + 1
490
- continued_comment.value << "\n" << child.value
497
+ continued_comment.lines + 1
498
+ continued_comment.value += ["\n"] + child.value
491
499
  next
492
500
  end
493
501
 
@@ -538,7 +546,7 @@ WARNING
538
546
  when ?$
539
547
  parse_variable(line)
540
548
  when COMMENT_CHAR
541
- parse_comment(line.text)
549
+ parse_comment(line)
542
550
  when DIRECTIVE_CHAR
543
551
  parse_directive(parent, line, root)
544
552
  when ESCAPE_CHAR
@@ -600,11 +608,19 @@ WARNING
600
608
  end
601
609
 
602
610
  def parse_comment(line)
603
- if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
604
- silent = line[1] == SASS_COMMENT_CHAR
605
- Tree::CommentNode.new(
606
- format_comment_text(line[2..-1], silent),
607
- silent)
611
+ if line.text[1] == CSS_COMMENT_CHAR || line.text[1] == SASS_COMMENT_CHAR
612
+ silent = line.text[1] == SASS_COMMENT_CHAR
613
+ if loud = line.text[2] == SASS_LOUD_COMMENT_CHAR
614
+ value = self.class.parse_interp(line.text, line.index, line.offset, :filename => @filename)
615
+ value[0].slice!(2) # get rid of the "!"
616
+ else
617
+ value = [line.text]
618
+ end
619
+ value = with_extracted_values(value) do |str|
620
+ str = str.gsub(/^#{line.comment_tab_str}/m, '')[2..-1] # get rid of // or /*
621
+ format_comment_text(str, silent)
622
+ end
623
+ Tree::CommentNode.new(value, silent, loud)
608
624
  else
609
625
  Tree::RuleNode.new(parse_interp(line))
610
626
  end
@@ -31,7 +31,7 @@ module Less
31
31
  WARNING: Sass doesn't support mixing in selector sequences.
32
32
  Replacing "#{sel}" with "@extend #{base}"
33
33
  WARNING
34
- env << Node::SassNode.new(Sass::Tree::CommentNode.new("// #{sel};", true))
34
+ env << Node::SassNode.new(Sass::Tree::CommentNode.new(["// #{sel};"], true, false))
35
35
  env << Node::SassNode.new(Sass::Tree::ExtendNode.new([base]))
36
36
  end
37
37
  end
@@ -89,14 +89,28 @@ module Sass
89
89
  end
90
90
 
91
91
  def process_comment(text, node)
92
- single_line = text =~ /^\/\//
93
- pre_str = single_line ? "" : @scanner.
94
- string[0...@scanner.pos].
95
- reverse[/.*?\*\/(.*?)($|\Z)/, 1].
96
- reverse.gsub(/[^\s]/, ' ')
97
- text = text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */' if single_line
98
- comment = Sass::Tree::CommentNode.new(pre_str + text, single_line)
99
- comment.line = @line - text.count("\n")
92
+ silent = text =~ /^\/\//
93
+ line = @line - text.count("\n")
94
+ if loud = text =~ %r{^/[/*]!}
95
+ value = Sass::Engine.parse_interp(text, line, @scanner.pos - text.size, :filename => @filename)
96
+ value[0].slice!(2) # get rid of the "!"
97
+ else
98
+ value = [text]
99
+ end
100
+
101
+ if silent
102
+ value = Sass::Util.with_extracted_values(value) do |str|
103
+ str.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */'
104
+ end
105
+ else
106
+ value.unshift(@scanner.
107
+ string[0...@scanner.pos].
108
+ reverse[/.*?\*\/(.*?)($|\Z)/, 1].
109
+ reverse.gsub(/[^\s]/, ' '))
110
+ end
111
+
112
+ comment = Sass::Tree::CommentNode.new(value, silent, loud)
113
+ comment.line = line
100
114
  node << comment
101
115
  end
102
116
 
@@ -777,8 +791,14 @@ MESSAGE
777
791
  end
778
792
 
779
793
  def str?
794
+ pos = @scanner.pos
795
+ line = @line
780
796
  @strs.push ""
781
797
  yield && @strs.last
798
+ rescue Sass::SyntaxError => e
799
+ @scanner.pos = pos
800
+ @line = line
801
+ nil
782
802
  ensure
783
803
  @strs.pop
784
804
  end
@@ -17,7 +17,7 @@ module Sass
17
17
  # @return [String] The text remaining in the scanner after all `#{`s have been processed
18
18
  def handle_interpolation(str)
19
19
  scan = StringScanner.new(str)
20
- yield scan while scan.scan(/(.*?)(\\*)\#\{/)
20
+ yield scan while scan.scan(/(.*?)(\\*)\#\{/m)
21
21
  scan.rest
22
22
  end
23
23
 
@@ -6,10 +6,19 @@ module Sass::Tree
6
6
  # @see Sass::Tree
7
7
  class CommentNode < Node
8
8
  # The text of the comment, not including `/*` and `*/`.
9
+ # Interspersed with {Sass::Script::Node}s representing `#{}`-interpolation
10
+ # if this is a loud comment.
9
11
  #
10
- # @return [String]
12
+ # @return [Array<String, Sass::Script::Node>]
11
13
  attr_accessor :value
12
14
 
15
+ # The text of the comment
16
+ # after any interpolated SassScript has been resolved.
17
+ # Only set once \{Tree::Visitors::Perform} has been run.
18
+ #
19
+ # @return [String]
20
+ attr_accessor :resolved_value
21
+
13
22
  # Whether the comment is loud.
14
23
  #
15
24
  # Loud comments start with ! and force the comment to be generated
@@ -23,14 +32,13 @@ module Sass::Tree
23
32
  # @return [Boolean]
24
33
  attr_accessor :silent
25
34
 
26
- # @param value [String] See \{#value}
35
+ # @param value [Array<String, Sass::Script::Node>] See \{#value}
27
36
  # @param silent [Boolean] See \{#silent}
28
- def initialize(value, silent)
29
- @lines = []
37
+ # @param loud [Boolean] See \{#loud}
38
+ def initialize(value, silent, loud)
39
+ @value = Sass::Util.with_extracted_values(value) {|str| normalize_indentation str}
30
40
  @silent = silent
31
- @value = normalize_indentation value
32
- @loud = @value =~ %r{^(/[\/\*])?!}
33
- @value.sub!("#{$1}!", $1.to_s) if @loud
41
+ @loud = loud
34
42
  super()
35
43
  end
36
44
 
@@ -40,7 +48,7 @@ module Sass::Tree
40
48
  # @return [Boolean] Whether or not this node and the other object
41
49
  # are the same
42
50
  def ==(other)
43
- self.class == other.class && value == other.value && silent == other.silent
51
+ self.class == other.class && value == other.value && silent == other.silent && loud == other.loud
44
52
  end
45
53
 
46
54
  # Returns `true` if this is a silent comment
@@ -57,9 +65,14 @@ module Sass::Tree
57
65
  end
58
66
  end
59
67
 
60
- # Returns whether this comment should be interpolated for dynamic comment generation.
61
- def evaluated?
62
- @loud
68
+ # Returns the number of lines in the comment.
69
+ #
70
+ # @return [Fixnum]
71
+ def lines
72
+ @value.inject(0) do |s, e|
73
+ next s + e.count("\n") if e.is_a?(String)
74
+ next s
75
+ end
63
76
  end
64
77
 
65
78
  private
@@ -32,7 +32,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
32
32
  visit(child) +
33
33
  if nxt &&
34
34
  (child.is_a?(Sass::Tree::CommentNode) &&
35
- child.line + child.value.count("\n") + 1 == nxt.line) ||
35
+ child.line + child.lines + 1 == nxt.line) ||
36
36
  (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
37
37
  child.line + 1 == nxt.line) ||
38
38
  (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
@@ -49,8 +49,13 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
49
49
  end
50
50
 
51
51
  def visit_comment(node)
52
+ value = node.value.map do |r|
53
+ next r if r.is_a?(String)
54
+ "\#{#{r.to_sass(@options)}}"
55
+ end.join
56
+
52
57
  content = if @format == :sass
53
- content = node.value.gsub(/\*\/$/, '').rstrip
58
+ content = value.gsub(/\*\/$/, '').rstrip
54
59
  if content =~ /\A[ \t]/
55
60
  # Re-indent SCSS comments like this:
56
61
  # /* foo
@@ -78,11 +83,11 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
78
83
  content.gsub!(/^/, tab_str)
79
84
  content.rstrip + "\n"
80
85
  else
81
- spaces = (' ' * [@tabs - node.value[/^ */].size, 0].max)
86
+ spaces = (' ' * [@tabs - value[/^ */].size, 0].max)
82
87
  content = if node.silent
83
- node.value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
88
+ value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
84
89
  else
85
- node.value
90
+ value
86
91
  end.gsub(/^/, spaces) + "\n"
87
92
  content
88
93
  end
@@ -53,12 +53,10 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
53
53
  # Removes this node from the tree if it's a silent comment.
54
54
  def visit_comment(node)
55
55
  return [] if node.invisible?
56
- if node.evaluated?
57
- node.value.gsub!(/(^|[^\\])\#\{([^}]*)\}/) do |md|
58
- $1+Sass::Script.parse($2, node.line, 0, node.options).perform(@environment).to_s
59
- end
60
- node.value = run_interp([Sass::Script::String.new(node.value)])
61
- end
56
+ check_for_loud_silent_comment node
57
+ check_for_comment_interp node
58
+ node.resolved_value = run_interp_no_strip(node.value)
59
+ node.resolved_value.gsub!(/\\([\\#])/, '\1')
62
60
  node
63
61
  end
64
62
 
@@ -278,14 +276,18 @@ END
278
276
 
279
277
  private
280
278
 
281
- def run_interp(text)
279
+ def run_interp_no_strip(text)
282
280
  text.map do |r|
283
281
  next r if r.is_a?(String)
284
282
  val = r.perform(@environment)
285
283
  # Interpolated strings should never render with quotes
286
284
  next val.value if val.is_a?(Sass::Script::String)
287
285
  val.to_s
288
- end.join.strip
286
+ end.join
287
+ end
288
+
289
+ def run_interp(text)
290
+ run_interp_no_strip(text).strip
289
291
  end
290
292
 
291
293
  def handle_include_loop!(node)
@@ -301,4 +303,30 @@ END
301
303
  end.join("\n")
302
304
  raise Sass::SyntaxError.new(msg)
303
305
  end
306
+
307
+ def check_for_loud_silent_comment(node)
308
+ return unless node.loud && node.silent
309
+ Sass::Util.sass_warn <<MESSAGE
310
+ WARNING:
311
+ On line #{node.line}#{" of '#{node.filename}'" if node.filename}
312
+ `//` comments will no longer be allowed to use the `!` flag in Sass 3.2.
313
+ Please change to `/*` comments.
314
+ MESSAGE
315
+ end
316
+
317
+ def check_for_comment_interp(node)
318
+ return if node.loud
319
+ node.value.each do |e|
320
+ next unless e.is_a?(String)
321
+ e.scan(/(\\*)#\{/) do |esc|
322
+ Sass::Util.sass_warn <<MESSAGE if esc.first.size.even?
323
+ WARNING:
324
+ On line #{node.line}#{" of '#{node.filename}'" if node.filename}
325
+ Comments will evaluate the contents of interpolations (\#{ ... }) in Sass 3.2.
326
+ Please escape the interpolation by adding a backslash before the `#`.
327
+ MESSAGE
328
+ return
329
+ end
330
+ end
331
+ end
304
332
  end
@@ -57,21 +57,11 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
57
57
 
58
58
  def visit_comment(node)
59
59
  return if node.invisible?
60
- spaces = (' ' * [@tabs - node.value[/^ */].size, 0].max)
60
+ spaces = (' ' * [@tabs - node.resolved_value[/^ */].size, 0].max)
61
61
 
62
- content = node.value.gsub(/^/, spaces).gsub(%r{^(\s*)//(.*)$}) do |md|
62
+ content = node.resolved_value.gsub(/^/, spaces).gsub(%r{^(\s*)//(.*)$}) do |md|
63
63
  "#{$1}/*#{$2} */"
64
64
  end
65
- if content =~ /[^\\]\#\{.*\}/
66
- Sass::Util.sass_warn <<MESSAGE
67
- WARNING:
68
- On line #{node.line}#{" of '#{node.filename}'" if node.filename}
69
- Comments will evaluate the contents of interpolations (\#{ ... }) in Sass 3.2.
70
- Please escape the interpolation by adding a backslash before the hash sign.
71
- MESSAGE
72
- elsif content =~ /\\\#\{.*\}/
73
- content.gsub!(/\\(\#\{.*\})/, '\1')
74
- end
75
65
  content.gsub!(/\n +(\* *(?!\/))?/, ' ') if (node.style == :compact || node.style == :compressed) && !node.loud
76
66
  content
77
67
  end
@@ -612,6 +612,57 @@ MSG
612
612
  '"' + obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]} + '"'
613
613
  end
614
614
 
615
+ # Extracts the non-string vlaues from an array containing both strings and non-strings.
616
+ # These values are replaced with escape sequences.
617
+ # This can be undone using \{#inject\_values}.
618
+ #
619
+ # This is useful e.g. when we want to do string manipulation
620
+ # on an interpolated string.
621
+ #
622
+ # The precise format of the resulting string is not guaranteed.
623
+ # However, it is guaranteed that newlines and whitespace won't be affected.
624
+ #
625
+ # @param arr [Array] The array from which values are extracted.
626
+ # @return [(String, Array)] The resulting string, and an array of extracted values.
627
+ def extract_values(arr)
628
+ values = []
629
+ return arr.map do |e|
630
+ next e.gsub('{', '{{') if e.is_a?(String)
631
+ values << e
632
+ next "{#{values.count - 1}}"
633
+ end.join, values
634
+ end
635
+
636
+ # Undoes \{#extract\_values} by transforming a string with escape sequences
637
+ # into an array of strings and non-string values.
638
+ #
639
+ # @param str [String] The string with escape sequences.
640
+ # @param values [Array] The array of values to inject.
641
+ # @return [Array] The array of strings and values.
642
+ def inject_values(str, values)
643
+ return [str.gsub('{{', '{')] if values.empty?
644
+ # Add an extra { so that we process the tail end of the string
645
+ result = (str + '{{').scan(/(.*?)(?:(\{\{)|\{(\d+)\})/m).map do |(pre, esc, n)|
646
+ [pre, esc ? '{' : '', n ? values[n.to_i] : '']
647
+ end.flatten(1)
648
+ result[-2] = '' # Get rid of the extra {
649
+ merge_adjacent_strings(result).reject {|s| s == ''}
650
+ end
651
+
652
+ # Allows modifications to be performed on the string form
653
+ # of an array containing both strings and non-strings.
654
+ #
655
+ # @param arr [Array] The array from which values are extracted.
656
+ # @yield [str] A block in which string manipulation can be done to the array.
657
+ # @yieldparam str [String] The string form of `arr`.
658
+ # @yieldreturn [String] The modified string.
659
+ # @return [Array] The modified, interpolated array.
660
+ def with_extracted_values(arr)
661
+ str, vals = extract_values(arr)
662
+ str = yield str
663
+ inject_values(str, vals)
664
+ end
665
+
615
666
  ## Static Method Stuff
616
667
 
617
668
  # The context in which the ERB for \{#def\_static\_method} will be run.
@@ -1131,17 +1131,13 @@ div
1131
1131
  SASS
1132
1132
  end
1133
1133
 
1134
- def test_loud_comment_conversion
1134
+ def test_loud_comment_conversion
1135
1135
  assert_renders(<<SASS, <<SCSS)
1136
1136
  /*! \#{"interpolated"}
1137
- /*!
1138
- * \#{"also interpolated"}
1139
1137
  SASS
1140
1138
  /*! \#{"interpolated"} */
1141
- /*!
1142
- * \#{"also interpolated"} */
1143
1139
  SCSS
1144
- assert_renders(<<SASS, <<SCSS)
1140
+ silence_warnings {assert_renders(<<SASS, <<SCSS)}
1145
1141
  //! \#{"interpolated"}
1146
1142
  //!
1147
1143
  //! \#{"also interpolated"}
@@ -1570,11 +1570,11 @@ foo
1570
1570
  */
1571
1571
  SASS
1572
1572
  end
1573
+
1573
1574
  def test_loud_comment_in_silent_comment
1574
- assert_equal <<CSS, render(<<SASS, :style => :compressed)
1575
+ silence_warnings {assert_equal <<CSS, render(<<SASS, :style => :compressed)}
1575
1576
  foo{color:blue;/* foo */
1576
1577
  /* bar */
1577
- /* */
1578
1578
  /* bip */
1579
1579
  /* baz */}
1580
1580
  CSS
@@ -1590,8 +1590,7 @@ SASS
1590
1590
 
1591
1591
  def test_loud_comment_is_evaluated
1592
1592
  assert_equal <<CSS, render(<<SASS)
1593
- /*
1594
- * Hue: 327.216deg */
1593
+ /* Hue: 327.216deg */
1595
1594
  CSS
1596
1595
  /*!
1597
1596
  Hue: \#{hue(#f836a0)}
@@ -2030,6 +2029,31 @@ CSS
2030
2029
 
2031
2030
  # Regression tests
2032
2031
 
2032
+ def test_interpolated_comment_in_mixin
2033
+ assert_equal <<CSS, render(<<SASS)
2034
+ /* color: red */
2035
+ .foo {
2036
+ color: red; }
2037
+
2038
+ /* color: blue */
2039
+ .foo {
2040
+ color: blue; }
2041
+
2042
+ /* color: green */
2043
+ .foo {
2044
+ color: green; }
2045
+ CSS
2046
+ =foo($var)
2047
+ /*! color: \#{$var}
2048
+ .foo
2049
+ color: $var
2050
+
2051
+ +foo(red)
2052
+ +foo(blue)
2053
+ +foo(green)
2054
+ SASS
2055
+ end
2056
+
2033
2057
  def test_parens_in_mixins
2034
2058
  assert_equal(<<CSS, render(<<SASS))
2035
2059
  .foo {
@@ -2308,7 +2332,16 @@ SASS
2308
2332
  WARNING:
2309
2333
  On line 1 of 'test_comment_interpolation_warning_inline.sass'
2310
2334
  Comments will evaluate the contents of interpolations (\#{ ... }) in Sass 3.2.
2311
- Please escape the interpolation by adding a backslash before the hash sign.
2335
+ Please escape the interpolation by adding a backslash before the `#`.
2336
+ END
2337
+ end
2338
+
2339
+ def test_loud_silent_comment_warning
2340
+ assert_warning(<<END) {render("//! \#{foo}")}
2341
+ WARNING:
2342
+ On line 1 of 'test_loud_silent_comment_warning_inline.sass'
2343
+ `//` comments will no longer be allowed to use the `!` flag in Sass 3.2.
2344
+ Please change to `/*` comments.
2312
2345
  END
2313
2346
  end
2314
2347
 
@@ -800,6 +800,12 @@ SCSS
800
800
  assert_selector_parses('E*:hover')
801
801
  end
802
802
 
803
+ def test_spaceless_combo_selectors
804
+ assert_equal "E > F {\n a: b; }\n", render("E>F { a: b;} ")
805
+ assert_equal "E ~ F {\n a: b; }\n", render("E~F { a: b;} ")
806
+ assert_equal "E + F {\n a: b; }\n", render("E+F { a: b;} ")
807
+ end
808
+
803
809
  ## Errors
804
810
 
805
811
  def test_invalid_directives
@@ -208,6 +208,18 @@ class UtilTest < Test::Unit::TestCase
208
208
  assert(set_eql?(s1, s2))
209
209
  end
210
210
 
211
+ def test_extract_and_inject_values
212
+ test = lambda {|arr| assert_equal(arr, with_extracted_values(arr) {|str| str})}
213
+
214
+ test[['foo bar']]
215
+ test[['foo {12} bar']]
216
+ test[['foo {{12} bar']]
217
+ test[['foo {{1', 12, '2} bar']]
218
+ test[['foo 1', 2, '{3', 4, 5, 6, '{7}', 8]]
219
+ test[['foo 1', [2, 3, 4], ' bar']]
220
+ test[['foo ', 1, "\n bar\n", [2, 3, 4], "\n baz"]]
221
+ end
222
+
211
223
  def test_caller_info
212
224
  assert_equal(["/tmp/foo.rb", 12, "fizzle"], caller_info("/tmp/foo.rb:12: in `fizzle'"))
213
225
  assert_equal(["/tmp/foo.rb", 12, nil], caller_info("/tmp/foo.rb:12"))
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: 19
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 3
8
8
  - 1
9
- - 8
10
- version: 3.1.8
9
+ - 9
10
+ version: 3.1.9
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-09-30 00:00:00 -07:00
20
+ date: 2011-10-06 00:00:00 -07:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency