lib/sass/script/parser.rb in sass-3.4.19 vs lib/sass/script/parser.rb in sass-3.4.20

- old
+ new

@@ -44,11 +44,12 @@ # Start two characters back to compensate for #{ start_pos = Sass::Source::Position.new(line, offset - 2) expr = assert_expr :expr assert_tok :end_interpolation expr = Sass::Script::Tree::Interpolation.new( - nil, expr, nil, !:wb, !:wa, !:originally_text, warn_for_color) + nil, expr, nil, !:wb, !:wa, :warn_for_color => warn_for_color) + check_for_interpolation expr expr.options = @options node(expr, start_pos) rescue Sass::SyntaxError => e e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] raise e @@ -60,10 +61,11 @@ # @raise [Sass::SyntaxError] if the expression isn't valid SassScript def parse expr = assert_expr :expr assert_done expr.options = @options + check_for_interpolation expr expr rescue Sass::SyntaxError => e e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] raise e end @@ -77,10 +79,11 @@ def parse_until(tokens) @stop_at = tokens expr = assert_expr :expr assert_done expr.options = @options + check_for_interpolation expr expr rescue Sass::SyntaxError => e e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] raise e end @@ -100,14 +103,30 @@ args, keywords, splat, kwarg_splat = mixin_arglist assert_tok(:rparen) end assert_done - args.each {|a| a.options = @options} - keywords.each {|k, v| v.options = @options} - splat.options = @options if splat - kwarg_splat.options = @options if kwarg_splat + args.each do |a| + check_for_interpolation a + a.options = @options + end + + keywords.each do |k, v| + check_for_interpolation v + v.options = @options + end + + if splat + check_for_interpolation splat + splat.options = @options + end + + if kwarg_splat + check_for_interpolation kwarg_splat + kwarg_splat.options = @options + end + return args, keywords, splat, kwarg_splat rescue Sass::SyntaxError => e e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] raise e end @@ -120,14 +139,24 @@ def parse_mixin_definition_arglist args, splat = defn_arglist!(false) assert_done args.each do |k, v| + check_for_interpolation k k.options = @options - v.options = @options if v + + if v + check_for_interpolation v + v.options = @options + end end - splat.options = @options if splat + + if splat + check_for_interpolation splat + splat.options = @options + end + return args, splat rescue Sass::SyntaxError => e e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] raise e end @@ -140,14 +169,24 @@ def parse_function_definition_arglist args, splat = defn_arglist!(true) assert_done args.each do |k, v| + check_for_interpolation k k.options = @options - v.options = @options if v + + if v + check_for_interpolation v + v.options = @options + end end - splat.options = @options if splat + + if splat + check_for_interpolation splat + splat.options = @options + end + return args, splat rescue Sass::SyntaxError => e e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] raise e end @@ -163,10 +202,11 @@ (peek.type == :funcall && peek.value.downcase == 'url')) lexer.expected!("string") end expr = assert_expr :funcall + check_for_interpolation expr expr.options = @options @lexer.unpeek! expr rescue Sass::SyntaxError => e e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] @@ -313,52 +353,112 @@ list end production :equals, :interpolation, :single_eq - def try_op_before_interp(op, prev = nil) + def try_op_before_interp(op, prev = nil, after_interp = false) return unless @lexer.peek && @lexer.peek.type == :begin_interpolation + unary = !prev && !after_interp wb = @lexer.whitespace?(op) str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]), op.source_range) + + deprecation = + case op.type + when :comma; :potential + when :div, :single_eq; :none + when :plus; unary ? :none : :immediate + when :minus; @lexer.whitespace?(@lexer.peek) ? :immediate : :none + else; :immediate + end + interp = node( - Script::Tree::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text), + Script::Tree::Interpolation.new( + prev, str, nil, wb, !:wa, :originally_text => true, :deprecation => deprecation), (prev || str).source_range.start_pos) interpolation(interp) end def try_ops_after_interp(ops, name, prev = nil) return unless @lexer.after_interpolation? op = try_toks(*ops) return unless op - interp = try_op_before_interp(op, prev) + interp = try_op_before_interp(op, prev, :after_interp) return interp if interp wa = @lexer.whitespace? str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]), op.source_range) str.line = @lexer.line + + deprecation = + case op.type + when :comma; :potential + when :div, :single_eq; :none + when :minus; @lexer.whitespace?(op) ? :immediate : :none + else; :immediate + end interp = node( - Script::Tree::Interpolation.new(prev, str, assert_expr(name), !:wb, wa, :originally_text), + Script::Tree::Interpolation.new( + prev, str, assert_expr(name), !:wb, wa, + :originally_text => true, :deprecation => deprecation), (prev || str).source_range.start_pos) interp end def interpolation(first = space) e = first while (interp = try_tok(:begin_interpolation)) wb = @lexer.whitespace?(interp) + char_before = @lexer.char(interp.pos - 1) mid = assert_expr :expr assert_tok :end_interpolation wa = @lexer.whitespace? + char_after = @lexer.char + + after = space + before_deprecation = e.is_a?(Script::Tree::Interpolation) ? e.deprecation : :none + after_deprecation = after.is_a?(Script::Tree::Interpolation) ? after.deprecation : :none + + deprecation = + if before_deprecation == :immediate || after_deprecation == :immediate || + # Warn for #{foo}$var and #{foo}(1) but not #{$foo}1. + (after && !wa && char_after =~ /[$(]/) || + # Warn for $var#{foo} and (a)#{foo} but not a#{foo}. + (e && !wb && is_unsafe_before?(e, char_before)) + :immediate + else + :potential + end + e = node( - Script::Tree::Interpolation.new(e, mid, space, wb, wa), - (e || mid).source_range.start_pos) + Script::Tree::Interpolation.new(e, mid, after, wb, wa, :deprecation => deprecation), + (e || interp).source_range.start_pos) end e end + # Returns whether `expr` is unsafe to include before an interpolation. + # + # @param expr [Node] The expression to check. + # @param char_before [String] The character immediately before the + # interpolation being checked (and presumably the last character of + # `expr`). + # @return [Boolean] + def is_unsafe_before?(expr, char_before) + # If the previous expression is an identifier or number, it's safe + # unless it was wrapped in parentheses. + if expr.is_a?(Script::Tree::Literal) && + (expr.value.is_a?(Script::Value::Number) || + (expr.value.is_a?(Script::Value::String) && expr.value.type == :identifier)) + return char_before == ')' + end + + # Otherwise, it's only safe if it was another interpolation. + !expr.is_a?(Script::Tree::Interpolation) + end + def space start_pos = source_position e = or_expr return unless e arr = [e] @@ -500,12 +600,13 @@ str = literal_node(first.value, first.source_range) return str unless try_tok(:string_interpolation) mid = assert_expr :expr assert_tok :end_interpolation last = assert_expr(:special_fun) - node(Tree::Interpolation.new(str, mid, last, false, false), - first.source_range.start_pos) + node( + Tree::Interpolation.new(str, mid, last, !:wb, !:wa), + first.source_range.start_pos) end def paren return variable unless try_tok(:lparen) start_pos = source_position @@ -536,10 +637,11 @@ def number tok = try_tok(:number) return selector unless tok num = tok.value + num.options = @options num.original = num.to_s literal_node(num, tok.source_range.start_pos) end def selector @@ -628,9 +730,44 @@ node.line = source_range.start_pos.line node.source_range = source_range node.filename = @options[:filename] node + end + + # Checks a script node for any immediately-deprecated interpolations, and + # emits warnings for them. + # + # @param node [Sass::Script::Tree::Node] + def check_for_interpolation(node) + nodes = [node] + until nodes.empty? + node = nodes.pop + unless node.is_a?(Sass::Script::Tree::Interpolation) && + node.deprecation == :immediate + nodes.concat node.children + next + end + + interpolation_deprecation(node) + end + end + + # Emits a deprecation warning for an interpolation node. + # + # @param node [Sass::Script::Tree::Node] + def interpolation_deprecation(interpolation) + return if @options[:_convert] + location = "on line #{interpolation.line}" + location << " of #{interpolation.filename}" if interpolation.filename + Sass::Util.sass_warn <<WARNING +DEPRECATION WARNING #{location}: \#{} interpolation near operators will be simplified +in a future version of Sass. To preserve the current behavior, use quotes: + + #{interpolation.to_quoted_equivalent.to_sass} + +You can use the sass-convert command to automatically fix most cases. +WARNING end end end end