sass 3.1.8 → 3.1.9

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