From 627ef9e29db0076844d59cabca256cee53e59f44 Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Thu, 14 Aug 2014 16:11:21 +0000 Subject: [PATCH] Optional line numbers for liquid errors --- lib/liquid.rb | 2 +- lib/liquid/block.rb | 2 +- lib/liquid/context.rb | 13 +++------ lib/liquid/errors.rb | 32 +++++++++++++++++++- lib/liquid/template.rb | 6 ++-- lib/liquid/{profiler => }/token.rb | 2 -- test/integration/error_handling_test.rb | 39 +++++++++++++++++++++++-- 7 files changed, 76 insertions(+), 20 deletions(-) rename lib/liquid/{profiler => }/token.rb (99%) diff --git a/lib/liquid.rb b/lib/liquid.rb index c1fac5f..cc56eb0 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -66,11 +66,11 @@ require 'liquid/standardfilters' require 'liquid/condition' require 'liquid/module_ex' require 'liquid/utils' +require 'liquid/token' # Load all the tags of the standard library # Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f } require 'liquid/profiler' -require 'liquid/profiler/token' require 'liquid/profiler/hooks' diff --git a/lib/liquid/block.rb b/lib/liquid/block.rb index 5503913..a53c282 100644 --- a/lib/liquid/block.rb +++ b/lib/liquid/block.rb @@ -144,7 +144,7 @@ module Liquid rescue MemoryError => e raise e rescue ::StandardError => e - output << (context.handle_error(e)) + output << (context.handle_error(e, token)) end end diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 9ff7260..0df9f77 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -91,17 +91,12 @@ module Liquid @interrupts.pop end - def handle_error(e) + + def handle_error(e, token) + e = Liquid::Error.error_with_line_number(e, token) errors.push(e) - raise if exception_handler && exception_handler.call(e) - - case e - when SyntaxError - "Liquid syntax error: #{e.message}" - else - "Liquid error: #{e.message}" - end + Liquid::Error.render(e) end def invoke(method, *args) diff --git a/lib/liquid/errors.rb b/lib/liquid/errors.rb index 85cb373..da03886 100644 --- a/lib/liquid/errors.rb +++ b/lib/liquid/errors.rb @@ -1,5 +1,35 @@ module Liquid - class Error < ::StandardError; end + class Error < ::StandardError + attr_accessor :line_number + + def self.render(e) + msg = if e.is_a?(Liquid::Error) && e.line_number + "#{e.line_number}: #{e.message}" + else + e.message + end + + case e + when SyntaxError + "Liquid syntax error: #{msg}" + else + "Liquid error: #{msg}" + end + end + + def self.error_with_line_number(e, token) + if e.is_a?(Liquid::Error) + e.set_line_number_from_token(token) + end + + e + end + + def set_line_number_from_token(token) + return unless token.respond_to?(:line_number) + self.line_number = token.line_number + end + end class ArgumentError < Error; end class ContextError < Error; end diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index d097b05..1237406 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -104,6 +104,7 @@ module Liquid # Returns self for easy chaining def parse(source, options = {}) @profiling = options.delete(:profile) + @line_numbers = options.delete(:line_numbers) || @profiling @root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options)) @warnings = nil self @@ -196,7 +197,7 @@ module Liquid end result.respond_to?(:join) ? result.join : result rescue Liquid::MemoryError => e - context.handle_error(e) + context.handle_error(e, nil) ensure @errors = context.errors end @@ -223,7 +224,7 @@ module Liquid end def calculate_line_numbers(raw_tokens) - return raw_tokens unless @profiling + return raw_tokens unless @line_numbers current_line = 1 raw_tokens.map do |token| @@ -247,6 +248,5 @@ module Liquid yield end end - end end diff --git a/lib/liquid/profiler/token.rb b/lib/liquid/token.rb similarity index 99% rename from lib/liquid/profiler/token.rb rename to lib/liquid/token.rb index 8c43da2..3a82014 100644 --- a/lib/liquid/profiler/token.rb +++ b/lib/liquid/token.rb @@ -1,6 +1,5 @@ module Liquid class Token < String - attr_reader :line_number def initialize(content, line_number) @@ -11,6 +10,5 @@ module Liquid def raw "" end - end end diff --git a/test/integration/error_handling_test.rb b/test/integration/error_handling_test.rb index 4f25f81..a66e43c 100644 --- a/test/integration/error_handling_test.rb +++ b/test/integration/error_handling_test.rb @@ -22,6 +22,39 @@ end class ErrorHandlingTest < Minitest::Test include Liquid + def test_templates_parsed_with_line_numbers_renders_them_in_errors + template = <<-LIQUID + Hello, + + {{ errors.standard_error }} will raise a standard error. + + Bla bla test. + + {{ errors.syntax_error }} will raise a syntax error. + + This is an argument error: {{ errors.argument_error }} + + Bla. + LIQUID + + expected = <<-TEXT + Hello, + + Liquid error: 3: standard error will raise a standard error. + + Bla bla test. + + Liquid syntax error: 7: syntax error will raise a syntax error. + + This is an argument error: Liquid error: 9: argument error + + Bla. + TEXT + + output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new) + assert_equal expected, output + end + def test_standard_error template = Liquid::Template.parse( ' {{ errors.standard_error }} ' ) assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new) @@ -59,7 +92,7 @@ class ErrorHandlingTest < Minitest::Test end end end - + def test_lax_unrecognized_operator template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax) assert_equal ' Liquid error: Unknown operator =! ', template.render @@ -91,8 +124,8 @@ class ErrorHandlingTest < Minitest::Test # Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError def test_exceptions_propagate assert_raises Exception do - template = Liquid::Template.parse( ' {{ errors.exception }} ' ) + template = Liquid::Template.parse('{{ errors.exception }}') template.render('errors' => ErrorDrop.new) end end -end # ErrorHandlingTest +end