sass 3.2.0.alpha.21 → 3.2.0.alpha.33

Sign up to get free protection for your applications and to get access to all the features.
data/REVISION CHANGED
@@ -1 +1 @@
1
- 4ddfd6971c9a04129f3a184c15868ecacf46120f
1
+ d56ebfe074b7f23e689330e32d9dd0dd4f7ec76d
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.0.alpha.21
1
+ 3.2.0.alpha.33
data/lib/sass/engine.rb CHANGED
@@ -95,7 +95,10 @@ module Sass
95
95
  #
96
96
  # `children`: `Array<Line>`
97
97
  # : The lines nested below this one.
98
- class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children)
98
+ #
99
+ # `comment_tab_str`: `String?`
100
+ # : The prefix indentation for this comment, if it is a comment.
101
+ class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children, :comment_tab_str)
99
102
  def comment?
100
103
  text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
101
104
  end
@@ -112,6 +115,10 @@ module Sass
112
115
  # which is not output as a CSS comment.
113
116
  SASS_COMMENT_CHAR = ?/
114
117
 
118
+ # The character that indicates that a comment allows interpolation
119
+ # and should be preserved even in `:compressed` mode.
120
+ SASS_LOUD_COMMENT_CHAR = ?!
121
+
115
122
  # The character that follows the general COMMENT_CHAR and designates a CSS comment,
116
123
  # which is embedded in the CSS document.
117
124
  CSS_COMMENT_CHAR = ?*
@@ -425,7 +432,8 @@ but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
425
432
  MSG
426
433
  end
427
434
 
428
- last.text << "\n" << $1
435
+ last.comment_tab_str ||= comment_tab_str
436
+ last.text << "\n" << line
429
437
  true
430
438
  end
431
439
 
@@ -488,11 +496,11 @@ MSG
488
496
  continued_rule = nil
489
497
  end
490
498
 
491
- if child.is_a?(Tree::CommentNode) && child.silent
499
+ if child.is_a?(Tree::CommentNode) && child.type == :silent
492
500
  if continued_comment &&
493
501
  child.line == continued_comment.line +
494
- continued_comment.value.count("\n") + 1
495
- continued_comment.value << "\n" << child.value
502
+ continued_comment.lines + 1
503
+ continued_comment.value += ["\n"] + child.value
496
504
  next
497
505
  end
498
506
 
@@ -543,7 +551,7 @@ WARNING
543
551
  when ?$
544
552
  parse_variable(line)
545
553
  when COMMENT_CHAR
546
- parse_comment(line.text)
554
+ parse_comment(line)
547
555
  when DIRECTIVE_CHAR
548
556
  parse_directive(parent, line, root)
549
557
  when ESCAPE_CHAR
@@ -605,11 +613,21 @@ WARNING
605
613
  end
606
614
 
607
615
  def parse_comment(line)
608
- if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
609
- silent = line[1] == SASS_COMMENT_CHAR
610
- Tree::CommentNode.new(
611
- format_comment_text(line[2..-1], silent),
612
- silent)
616
+ if line.text[1] == CSS_COMMENT_CHAR || line.text[1] == SASS_COMMENT_CHAR
617
+ silent = line.text[1] == SASS_COMMENT_CHAR
618
+ loud = !silent && line.text[2] == SASS_LOUD_COMMENT_CHAR
619
+ if silent
620
+ value = [line.text]
621
+ else
622
+ value = self.class.parse_interp(line.text, line.index, line.offset, :filename => @filename)
623
+ value[0].slice!(2) if loud # get rid of the "!"
624
+ end
625
+ value = with_extracted_values(value) do |str|
626
+ str = str.gsub(/^#{line.comment_tab_str}/m, '')[2..-1] # get rid of // or /*
627
+ format_comment_text(str, silent)
628
+ end
629
+ type = if silent then :silent elsif loud then :loud else :normal end
630
+ Tree::CommentNode.new(value, type)
613
631
  else
614
632
  Tree::RuleNode.new(parse_interp(line))
615
633
  end
@@ -25,11 +25,7 @@ module Sass
25
25
  # @param parent [Environment] See \{#parent}
26
26
  def initialize(parent = nil)
27
27
  @parent = parent
28
- unless parent
29
- @stack = []
30
- @mixins_in_use = Set.new
31
- set_var("important", Script::String.new("!important"))
32
- end
28
+ set_var("important", Script::String.new("!important")) unless parent
33
29
  end
34
30
 
35
31
  # The environment of the caller of this environment's mixin or function.
@@ -53,72 +49,6 @@ module Sass
53
49
  @options || parent_options || {}
54
50
  end
55
51
 
56
- # Push a new stack frame onto the mixin/include stack.
57
- #
58
- # @param frame_info [{Symbol => Object}]
59
- # Frame information has the following keys:
60
- #
61
- # `:filename`
62
- # : The name of the file in which the lexical scope changed.
63
- #
64
- # `:mixin`
65
- # : The name of the mixin in which the lexical scope changed,
66
- # or `nil` if it wasn't within in a mixin.
67
- #
68
- # `:line`
69
- # : The line of the file on which the lexical scope changed. Never nil.
70
- def push_frame(frame_info)
71
- top_of_stack = stack.last
72
- if top_of_stack && top_of_stack.delete(:prepared)
73
- top_of_stack.merge!(frame_info)
74
- else
75
- stack.push(top_of_stack = frame_info)
76
- end
77
- mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin] && !top_of_stack[:prepared]
78
- end
79
-
80
- # Like \{#push\_frame}, but next time a stack frame is pushed,
81
- # it will be merged with this frame.
82
- #
83
- # @param frame_info [{Symbol => Object}] Same as for \{#push\_frame}.
84
- def prepare_frame(frame_info)
85
- push_frame(frame_info.merge(:prepared => true))
86
- end
87
-
88
- # Pop a stack frame from the mixin/include stack.
89
- def pop_frame
90
- stack.pop if stack.last && stack.last[:prepared]
91
- popped = stack.pop
92
- mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
93
- end
94
-
95
- # A list of stack frames in the mixin/include stack.
96
- # The last element in the list is the most deeply-nested frame.
97
- #
98
- # @return [Array<{Symbol => Object}>] The stack frames,
99
- # of the form passed to \{#push\_frame}.
100
- def stack
101
- @stack ||= @parent.stack
102
- end
103
-
104
- # A set of names of mixins currently present in the stack.
105
- #
106
- # @return [Set<String>] The mixin names.
107
- def mixins_in_use
108
- @mixins_in_use ||= @parent.mixins_in_use
109
- end
110
-
111
- def stack_trace
112
- trace = []
113
- stack.reverse.each_with_index do |entry, i|
114
- msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
115
- msg << " of #{entry[:filename] || "an unknown file"}"
116
- msg << ", in `#{entry[:mixin]}'" if entry[:mixin]
117
- trace << msg
118
- end
119
- trace
120
- end
121
-
122
52
  private
123
53
 
124
54
  def parent_options
data/lib/sass/exec.rb CHANGED
@@ -385,7 +385,7 @@ MSG
385
385
 
386
386
  dirs, files = @args.map {|name| split_colon_path(name)}.
387
387
  partition {|i, _| File.directory? i}
388
- files.map! {|from, to| [from, to || from.gsub(/\..*?$/, '.css')]}
388
+ files.map! {|from, to| [from, to || from.gsub(/\.[^.]*?$/, '.css')]}
389
389
  dirs.map! {|from, to| [from, to || from]}
390
390
  ::Sass::Plugin.options[:template_location] = dirs
391
391
 
data/lib/sass/less.rb CHANGED
@@ -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};"], :silent))
35
35
  env << Node::SassNode.new(Sass::Tree::ExtendNode.new([base]))
36
36
  end
37
37
  end
@@ -89,14 +89,24 @@ 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
+ loud = !silent && text =~ %r{^/[/*]!}
94
+ line = @line - text.count("\n")
95
+
96
+ if silent
97
+ value = [text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */']
98
+ else
99
+ value = Sass::Engine.parse_interp(text, line, @scanner.pos - text.size, :filename => @filename)
100
+ value[0].slice!(2) if loud # get rid of the "!"
101
+ value.unshift(@scanner.
102
+ string[0...@scanner.pos].
103
+ reverse[/.*?\*\/(.*?)($|\Z)/, 1].
104
+ reverse.gsub(/[^\s]/, ' '))
105
+ end
106
+
107
+ type = if silent then :silent elsif loud then :loud else :normal end
108
+ comment = Sass::Tree::CommentNode.new(value, type)
109
+ comment.line = line
100
110
  node << comment
101
111
  end
102
112
 
@@ -779,8 +789,14 @@ MESSAGE
779
789
  end
780
790
 
781
791
  def str?
792
+ pos = @scanner.pos
793
+ line = @line
782
794
  @strs.push ""
783
795
  yield && @strs.last
796
+ rescue Sass::SyntaxError => e
797
+ @scanner.pos = pos
798
+ @line = line
799
+ nil
784
800
  ensure
785
801
  @strs.pop
786
802
  end
data/lib/sass/shared.rb CHANGED
@@ -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,31 +6,31 @@ 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
 
13
- # Whether the comment is loud.
14
- #
15
- # Loud comments start with ! and force the comment to be generated
16
- # irrespective of compilation settings or the comment syntax used.
15
+ # The text of the comment
16
+ # after any interpolated SassScript has been resolved.
17
+ # Only set once \{Tree::Visitors::Perform} has been run.
17
18
  #
18
- # @return [Boolean]
19
- attr_accessor :loud
19
+ # @return [String]
20
+ attr_accessor :resolved_value
20
21
 
21
- # Whether or not the comment is silent (that is, doesn't output to CSS).
22
+ # The type of the comment. `:silent` means it's never output to CSS,
23
+ # `:normal` means it's output in every compile mode except `:compressed`,
24
+ # and `:loud` means it's output even in `:compressed`.
22
25
  #
23
- # @return [Boolean]
24
- attr_accessor :silent
26
+ # @return [Symbol]
27
+ attr_accessor :type
25
28
 
26
- # @param value [String] See \{#value}
27
- # @param silent [Boolean] See \{#silent}
28
- def initialize(value, silent)
29
- @lines = []
30
- @silent = silent
31
- @value = normalize_indentation value
32
- @loud = @value =~ %r{^(/[\/\*])?!}
33
- @value.sub!("#{$1}!", $1.to_s) if @loud
29
+ # @param value [Array<String, Sass::Script::Node>] See \{#value}
30
+ # @param type [Symbol] See \{#type}
31
+ def initialize(value, type)
32
+ @value = Sass::Util.with_extracted_values(value) {|str| normalize_indentation str}
33
+ @type = type
34
34
  super()
35
35
  end
36
36
 
@@ -40,7 +40,7 @@ module Sass::Tree
40
40
  # @return [Boolean] Whether or not this node and the other object
41
41
  # are the same
42
42
  def ==(other)
43
- self.class == other.class && value == other.value && silent == other.silent
43
+ self.class == other.class && value == other.value && type == other.type
44
44
  end
45
45
 
46
46
  # Returns `true` if this is a silent comment
@@ -50,16 +50,21 @@ module Sass::Tree
50
50
  #
51
51
  # @return [Boolean]
52
52
  def invisible?
53
- if @loud
54
- return false
55
- else
56
- @silent || (style == :compressed)
53
+ case @type
54
+ when :loud; false
55
+ when :silent; true
56
+ else; style == :compressed
57
57
  end
58
58
  end
59
59
 
60
- # Returns whether this comment should be interpolated for dynamic comment generation.
61
- def evaluated?
62
- @loud
60
+ # Returns the number of lines in the comment.
61
+ #
62
+ # @return [Fixnum]
63
+ def lines
64
+ @value.inject(0) do |s, e|
65
+ next s + e.count("\n") if e.is_a?(String)
66
+ next s
67
+ end
63
68
  end
64
69
 
65
70
  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
@@ -66,7 +71,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
66
71
  else
67
72
  content.gsub!(/\n( \*|\/\/)/, "\n ")
68
73
  spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
69
- sep = node.silent ? "\n//" : "\n *"
74
+ sep = node.type == :silent ? "\n//" : "\n *"
70
75
  if spaces >= 2
71
76
  content.gsub(/\n /, sep)
72
77
  else
@@ -74,25 +79,19 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
74
79
  end
75
80
  end
76
81
 
77
- content.gsub!(/\A\/\*/, '//') if node.silent
82
+ content.gsub!(/\A\/\*/, '//') if node.type == :silent
78
83
  content.gsub!(/^/, tab_str)
79
84
  content.rstrip + "\n"
80
85
  else
81
- spaces = (' ' * [@tabs - node.value[/^ */].size, 0].max)
82
- content = if node.silent
83
- node.value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
86
+ spaces = (' ' * [@tabs - value[/^ */].size, 0].max)
87
+ content = if node.type == :silent
88
+ value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
84
89
  else
85
- node.value
90
+ value
86
91
  end.gsub(/^/, spaces) + "\n"
87
92
  content
88
93
  end
89
- if node.loud
90
- if node.silent
91
- content.gsub!(%r{^\s*(//!?)}, '//!')
92
- else
93
- content.sub!(%r{^\s*(/\*)}, '/*!')
94
- end
95
- end
94
+ content.sub!(%r{^\s*(/\*)}, '/*!') if node.type == :loud
96
95
  content
97
96
  end
98
97
 
@@ -200,7 +199,7 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
200
199
  elsif @format == :scss
201
200
  name = selector_to_scss(node.rule)
202
201
  res = name + yield
203
- if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.silent
202
+ if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.type == :silent
204
203
  res.slice!(-3..-1)
205
204
  res << "\n" << tab_str << "}\n"
206
205
  end
@@ -43,7 +43,7 @@ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
43
43
  yield
44
44
  end
45
45
 
46
- def visit_mixin_def(node)
46
+ def visit_mixindef(node)
47
47
  node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
48
48
  yield
49
49
  end
@@ -11,6 +11,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
11
11
 
12
12
  def initialize(env)
13
13
  @environment = env
14
+ # Stack trace information, including mixin includes and imports.
15
+ @stack = []
14
16
  end
15
17
 
16
18
  # If an exception is raised, this adds proper metadata to the backtrace.
@@ -53,12 +55,8 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
53
55
  # Removes this node from the tree if it's a silent comment.
54
56
  def visit_comment(node)
55
57
  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
58
+ node.resolved_value = run_interp_no_strip(node.value)
59
+ node.resolved_value.gsub!(/\\([\\#])/, '\1')
62
60
  node
63
61
  end
64
62
 
@@ -140,8 +138,9 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
140
138
  return Sass::Tree::DirectiveNode.new("@import url(#{path})")
141
139
  end
142
140
 
143
- @environment.push_frame(:filename => node.filename, :line => node.line)
141
+ @stack.push(:filename => node.filename, :line => node.line)
144
142
  root = node.imported_file.to_tree
143
+ Sass::Tree::Visitors::CheckNesting.visit(root)
145
144
  node.children = root.children.map {|c| visit(c)}.flatten
146
145
  node
147
146
  rescue Sass::SyntaxError => e
@@ -149,7 +148,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
149
148
  e.add_backtrace(:filename => node.filename, :line => node.line)
150
149
  raise e
151
150
  ensure
152
- @environment.pop_frame
151
+ @stack.pop unless path
153
152
  end
154
153
 
155
154
  # Loads a mixin into the environment.
@@ -161,11 +160,11 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
161
160
 
162
161
  # Runs a mixin.
163
162
  def visit_mixin(node)
164
- handle_include_loop!(node) if @environment.mixins_in_use.include?(node.name)
163
+ include_loop = true
164
+ handle_include_loop!(node) if @stack.any? {|e| e[:name] == node.name}
165
+ include_loop = false
165
166
 
166
- original_env = @environment
167
- original_env.push_frame(:filename => node.filename, :line => node.line)
168
- original_env.prepare_frame(:mixin => node.name)
167
+ @stack.push(:filename => node.filename, :line => node.line, :name => node.name)
169
168
  raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin = @environment.mixin(node.name)
170
169
 
171
170
  if node.children.any? && !mixin.has_content
@@ -200,24 +199,25 @@ END
200
199
  raise Sass::SyntaxError.new("Mixin #{node.name} is missing parameter #{var.inspect}.") unless env.var(var.name)
201
200
  env
202
201
  end
203
- environment.caller = Sass::Environment.new(original_env)
202
+ environment.caller = Sass::Environment.new(@environment)
204
203
  environment.content = node.children if node.has_children
205
204
 
206
205
  trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
207
206
  with_environment(environment) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
208
207
  trace_node
209
208
  rescue Sass::SyntaxError => e
210
- if original_env # Don't add backtrace info if this is an @include loop
209
+ unless include_loop
211
210
  e.modify_backtrace(:mixin => node.name, :line => node.line)
212
211
  e.add_backtrace(:line => node.line)
213
212
  end
214
213
  raise e
215
214
  ensure
216
- original_env.pop_frame if original_env
215
+ @stack.pop unless include_loop
217
216
  end
218
217
 
219
218
  def visit_content(node)
220
219
  raise Sass::SyntaxError.new("No @content passed.") unless content = @environment.content
220
+ @stack.push(:filename => node.filename, :line => node.line, :name => '@content')
221
221
  trace_node = Sass::Tree::TraceNode.from_node('@content', node)
222
222
  with_environment(@environment.caller) {trace_node.children = content.map {|c| visit(c.dup)}.flatten}
223
223
  trace_node
@@ -225,6 +225,8 @@ END
225
225
  e.modify_backtrace(:mixin => '@content', :line => node.line)
226
226
  e.add_backtrace(:line => node.line)
227
227
  raise e
228
+ ensure
229
+ @stack.pop if content
228
230
  end
229
231
 
230
232
  # Runs any SassScript that may be embedded in a property.
@@ -246,9 +248,9 @@ END
246
248
  parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.filename, node.line)
247
249
  node.parsed_rules ||= parser.parse_selector
248
250
  if node.options[:trace_selectors]
249
- @environment.push_frame(:filename => node.filename, :line => node.line)
250
- node.stack_trace = @environment.stack_trace
251
- @environment.pop_frame
251
+ @stack.push(:filename => node.filename, :line => node.line)
252
+ node.stack_trace = stack_trace
253
+ @stack.pop
252
254
  end
253
255
  yield
254
256
  end
@@ -263,17 +265,17 @@ END
263
265
 
264
266
  # Prints the expression to STDERR with a stylesheet trace.
265
267
  def visit_warn(node)
266
- @environment.push_frame(:filename => node.filename, :line => node.line)
268
+ @stack.push(:filename => node.filename, :line => node.line)
267
269
  res = node.expr.perform(@environment)
268
270
  res = res.value if res.is_a?(Sass::Script::String)
269
271
  msg = "WARNING: #{res}\n "
270
- msg << @environment.stack_trace.join("\n ")
272
+ msg << stack_trace.join("\n ")
271
273
  # JRuby doesn't automatically add a newline for #warn
272
274
  msg << (RUBY_PLATFORM =~ /java/ ? "\n\n" : "\n")
273
275
  Sass::Util.sass_warn msg
274
276
  []
275
277
  ensure
276
- @environment.pop_frame
278
+ @stack.pop
277
279
  end
278
280
 
279
281
  # Runs the child nodes until the continuation expression becomes false.
@@ -295,25 +297,52 @@ END
295
297
 
296
298
  private
297
299
 
298
- def run_interp(text)
300
+ def stack_trace
301
+ trace = []
302
+ stack = @stack.map {|e| e.dup}.reverse
303
+ stack.each_cons(2) {|(e1, e2)| e1[:caller] = e2[:name]; [e1, e2]}
304
+ stack.each_with_index do |entry, i|
305
+ msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
306
+ msg << " of #{entry[:filename] || "an unknown file"}"
307
+ msg << ", in `#{entry[:caller]}'" if entry[:caller]
308
+ trace << msg
309
+ end
310
+ trace
311
+ end
312
+
313
+ def run_interp_no_strip(text)
299
314
  text.map do |r|
300
315
  next r if r.is_a?(String)
301
316
  val = r.perform(@environment)
302
317
  # Interpolated strings should never render with quotes
303
318
  next val.value if val.is_a?(Sass::Script::String)
304
319
  val.to_s
305
- end.join.strip
320
+ end.join
321
+ end
322
+
323
+ def run_interp(text)
324
+ run_interp_no_strip(text).strip
306
325
  end
307
326
 
308
327
  def handle_include_loop!(node)
309
328
  msg = "An @include loop has been found:"
310
- mixins = @environment.stack.map {|s| s[:mixin]}.compact
311
- if mixins.size == 2 && mixins[0] == mixins[1]
312
- raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself")
329
+ content_count = 0
330
+ mixins = @stack.reverse.map {|s| s[:name]}.compact.select do |s|
331
+ if s == '@content'
332
+ content_count += 1
333
+ false
334
+ elsif content_count > 0
335
+ content_count -= 1
336
+ false
337
+ else
338
+ true
339
+ end
313
340
  end
314
341
 
315
- mixins << node.name
316
- msg << "\n" << Sass::Util.enum_cons(mixins, 2).map do |m1, m2|
342
+ return if mixins.empty?
343
+ raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1
344
+
345
+ msg << "\n" << Sass::Util.enum_cons(mixins.reverse + [node.name], 2).map do |m1, m2|
317
346
  " #{m1} includes #{m2}"
318
347
  end.join("\n")
319
348
  raise Sass::SyntaxError.new(msg)
@@ -50,7 +50,7 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
50
50
  yield
51
51
  end
52
52
 
53
- def visit_mixin_def(node)
53
+ def visit_mixindef(node)
54
54
  node.args.each do |k, v|
55
55
  k.options = @options
56
56
  v.options = @options if v
@@ -57,22 +57,12 @@ 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
- content.gsub!(/\n +(\* *(?!\/))?/, ' ') if (node.style == :compact || node.style == :compressed) && !node.loud
65
+ content.gsub!(/\n +(\* *(?!\/))?/, ' ') if (node.style == :compact || node.style == :compressed) && node.type != :loud
76
66
  content
77
67
  end
78
68
 
data/lib/sass/util.rb CHANGED
@@ -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.
data/test/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ gem 'rake'
4
+ gem 'less', '< 2.0.0'
data/test/Gemfile.lock ADDED
@@ -0,0 +1,19 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ less (1.2.21)
5
+ mutter (>= 0.4.2)
6
+ treetop (>= 1.4.2)
7
+ mutter (0.5.3)
8
+ polyglot (0.3.2)
9
+ rake (0.9.2)
10
+ treetop (1.4.10)
11
+ polyglot
12
+ polyglot (>= 0.3.1)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ less (< 2.0.0)
19
+ rake
@@ -1131,24 +1131,11 @@ 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
- SCSS
1144
- assert_renders(<<SASS, <<SCSS)
1145
- //! \#{"interpolated"}
1146
- //!
1147
- //! \#{"also interpolated"}
1148
- SASS
1149
- //! \#{"interpolated"}
1150
- //!
1151
- //! \#{"also interpolated"}
1152
1139
  SCSS
1153
1140
  end
1154
1141
 
@@ -265,7 +265,7 @@ SASS
265
265
  end
266
266
 
267
267
  def test_imported_exception
268
- [1, 2, 3, 4].each do |i|
268
+ [1, 2, 3, 4, 5].each do |i|
269
269
  begin
270
270
  Sass::Engine.new("@import bork#{i}", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
271
271
  rescue Sass::SyntaxError => err
@@ -287,7 +287,7 @@ SASS
287
287
  end
288
288
 
289
289
  def test_double_imported_exception
290
- [1, 2, 3, 4].each do |i|
290
+ [1, 2, 3, 4, 5].each do |i|
291
291
  begin
292
292
  Sass::Engine.new("@import nested_bork#{i}", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
293
293
  rescue Sass::SyntaxError => err
@@ -1576,28 +1576,10 @@ foo
1576
1576
  */
1577
1577
  SASS
1578
1578
  end
1579
- def test_loud_comment_in_silent_comment
1580
- assert_equal <<CSS, render(<<SASS, :style => :compressed)
1581
- foo{color:blue;/* foo */
1582
- /* bar */
1583
- /* */
1584
- /* bip */
1585
- /* baz */}
1586
- CSS
1587
- foo
1588
- color: blue
1589
- //! foo
1590
- //! bar
1591
- //!
1592
- bip
1593
- baz
1594
- SASS
1595
- end
1596
1579
 
1597
1580
  def test_loud_comment_is_evaluated
1598
1581
  assert_equal <<CSS, render(<<SASS)
1599
- /*
1600
- * Hue: 327.216deg */
1582
+ /* Hue: 327.216deg */
1601
1583
  CSS
1602
1584
  /*!
1603
1585
  Hue: \#{hue(#f836a0)}
@@ -2036,6 +2018,31 @@ CSS
2036
2018
 
2037
2019
  # Regression tests
2038
2020
 
2021
+ def test_interpolated_comment_in_mixin
2022
+ assert_equal <<CSS, render(<<SASS)
2023
+ /* color: red */
2024
+ .foo {
2025
+ color: red; }
2026
+
2027
+ /* color: blue */
2028
+ .foo {
2029
+ color: blue; }
2030
+
2031
+ /* color: green */
2032
+ .foo {
2033
+ color: green; }
2034
+ CSS
2035
+ =foo($var)
2036
+ /*! color: \#{$var}
2037
+ .foo
2038
+ color: $var
2039
+
2040
+ +foo(red)
2041
+ +foo(blue)
2042
+ +foo(green)
2043
+ SASS
2044
+ end
2045
+
2039
2046
  def test_parens_in_mixins
2040
2047
  assert_equal(<<CSS, render(<<SASS))
2041
2048
  .foo {
@@ -2309,15 +2316,6 @@ CSS
2309
2316
  SASS
2310
2317
  end
2311
2318
 
2312
- def test_comment_interpolation_warning
2313
- assert_warning(<<END) {render("/* \#{foo}")}
2314
- WARNING:
2315
- On line 1 of 'test_comment_interpolation_warning_inline.sass'
2316
- Comments will evaluate the contents of interpolations (\#{ ... }) in Sass 3.2.
2317
- Please escape the interpolation by adding a backslash before the hash sign.
2318
- END
2319
- end
2320
-
2321
2319
  def test_loud_comment_interpolations_can_be_escaped
2322
2320
  assert_equal <<CSS, render(<<SASS)
2323
2321
  /* \#{foo} */
@@ -8,7 +8,7 @@ class SassPluginTest < Test::Unit::TestCase
8
8
  @@templates = %w{
9
9
  complex script parent_ref import scss_import alt
10
10
  subdir/subdir subdir/nested_subdir/nested_subdir
11
- options
11
+ options import_content
12
12
  }
13
13
  @@templates += %w[import_charset import_charset_ibm866] unless Sass::Util.ruby1_8?
14
14
  @@templates << 'import_charset_1_8' if Sass::Util.ruby1_8?
@@ -0,0 +1 @@
1
+ a { b: c; }
@@ -802,6 +802,12 @@ SCSS
802
802
  assert_selector_parses('E*:hover')
803
803
  end
804
804
 
805
+ def test_spaceless_combo_selectors
806
+ assert_equal "E > F {\n a: b; }\n", render("E>F { a: b;} ")
807
+ assert_equal "E ~ F {\n a: b; }\n", render("E~F { a: b;} ")
808
+ assert_equal "E + F {\n a: b; }\n", render("E+F { a: b;} ")
809
+ end
810
+
805
811
  ## Errors
806
812
 
807
813
  def test_invalid_directives
@@ -0,0 +1,3 @@
1
+ @mixin foo
2
+ a
3
+ @content
@@ -0,0 +1,3 @@
1
+ foo
2
+ @function bar($a)
3
+ @return $a
@@ -0,0 +1,4 @@
1
+ @import imported_content
2
+
3
+ @include foo
4
+ b: c
@@ -0,0 +1,2 @@
1
+
2
+ @import bork5
@@ -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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sass
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0.alpha.21
4
+ version: 3.2.0.alpha.33
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Weizenbaum
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2011-09-14 00:00:00 -04:00
14
+ date: 2011-10-06 00:00:00 -04:00
15
15
  default_executable:
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
@@ -229,6 +229,7 @@ files:
229
229
  - test/sass/results/units.css
230
230
  - test/sass/results/warn.css
231
231
  - test/sass/results/warn_imported.css
232
+ - test/sass/results/import_content.css
232
233
  - test/sass/script_conversion_test.rb
233
234
  - test/sass/script_test.rb
234
235
  - test/sass/scss/css_test.rb
@@ -277,10 +278,16 @@ files:
277
278
  - test/sass/templates/units.sass
278
279
  - test/sass/templates/warn.sass
279
280
  - test/sass/templates/warn_imported.sass
281
+ - test/sass/templates/_imported_content.sass
282
+ - test/sass/templates/bork5.sass
283
+ - test/sass/templates/import_content.sass
284
+ - test/sass/templates/nested_bork5.sass
280
285
  - test/sass/test_helper.rb
281
286
  - test/sass/util/subset_map_test.rb
282
287
  - test/sass/util_test.rb
283
288
  - test/test_helper.rb
289
+ - test/Gemfile
290
+ - test/Gemfile.lock
284
291
  - extra/update_watch.rb
285
292
  - Rakefile
286
293
  - init.rb