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

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