From 23554f73cab9c23427fe1a93989ff927c5fce57d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sun, 21 Aug 2022 11:02:48 -0700 Subject: [PATCH] Unify Error and HamlError as requested by my old TODO comment. Related to https://github.com/haml/haml/issues/1082, while not addressing the issue yet. --- lib/haml/compiler.rb | 2 +- lib/haml/error.rb | 54 ++++++++++++++++++++++++++++++-- lib/haml/haml_error.rb | 66 --------------------------------------- lib/haml/parser.rb | 58 +++++++++++++++++----------------- test/hamlit/error_test.rb | 8 ++--- test/test_helper.rb | 2 +- 6 files changed, 87 insertions(+), 103 deletions(-) delete mode 100644 lib/haml/haml_error.rb diff --git a/lib/haml/compiler.rb b/lib/haml/compiler.rb index ea0f58b8..51b22d4c 100644 --- a/lib/haml/compiler.rb +++ b/lib/haml/compiler.rb @@ -22,7 +22,7 @@ module Haml end def call(ast) - return runtime_error(ast) if ast.is_a?(HamlError) + return runtime_error(ast) if ast.is_a?(Error) compile(ast) rescue Error => e runtime_error(e) diff --git a/lib/haml/error.rb b/lib/haml/error.rb index 74d38e4c..04d174cd 100644 --- a/lib/haml/error.rb +++ b/lib/haml/error.rb @@ -1,16 +1,66 @@ # frozen_string_literal: true module Haml - # TODO: unify Haml::Error (former Hamlit::Error) and Haml::HamlError (former Haml::Error) class Error < StandardError + MESSAGES = { + bad_script_indent: '"%s" is indented at wrong level: expected %d, but was at %d.', + cant_run_filter: 'Can\'t run "%s" filter; you must require its dependencies first', + cant_use_tabs_and_spaces: "Indentation can't use both tabs and spaces.", + deeper_indenting: "The line was indented %d levels deeper than the previous line.", + filter_not_defined: 'Filter "%s" is not defined.', + gem_install_filter_deps: '"%s" filter\'s %s dependency missing: try installing it or adding it to your Gemfile', + illegal_element: "Illegal element: classes and ids must have values.", + illegal_nesting_content: "Illegal nesting: nesting within a tag that already has content is illegal.", + illegal_nesting_header: "Illegal nesting: nesting within a header command is illegal.", + illegal_nesting_line: "Illegal nesting: content can't be both given on the same line as %%%s and nested within it.", + illegal_nesting_plain: "Illegal nesting: nesting within plain text is illegal.", + illegal_nesting_self_closing: "Illegal nesting: nesting within a self-closing tag is illegal.", + inconsistent_indentation: "Inconsistent indentation: %s used for indentation, but the rest of the document was indented using %s.", + indenting_at_start: "Indenting at the beginning of the document is illegal.", + install_haml_contrib: 'To use the "%s" filter, please install the haml-contrib gem.', + invalid_attribute_list: 'Invalid attribute list: %s.', + invalid_filter_name: 'Invalid filter name ":%s".', + invalid_tag: 'Invalid tag: "%s".', + missing_if: 'Got "%s" with no preceding "if"', + no_ruby_code: "There's no Ruby code for %s to evaluate.", + self_closing_content: "Self-closing tags can't have content.", + unbalanced_brackets: 'Unbalanced brackets.', + no_end: <<-END +You don't need to use "- end" in Haml. Un-indent to close a block: +- if foo? + %strong Foo! +- else + Not foo. +%p This line is un-indented, so it isn't part of the "if" block +END + }.freeze + + def self.message(key, *args) + string = MESSAGES[key] or raise "[HAML BUG] No error messages for #{key}" + (args.empty? ? string : string % args).rstrip + end + + # The line of the template on which the error occurred. + # + # @return [Fixnum] attr_reader :line + # @param message [String] The error message + # @param line [Fixnum] See \{#line} def initialize(message = nil, line = nil) super(message) @line = line end end + # SyntaxError is the type of exception raised when Haml encounters an + # ill-formatted document. + # It's not particularly interesting, + # except in that it's a subclass of {Haml::Error}. class SyntaxError < Error; end - class InternalError < Error; end + + # An invalid filter name was used. class FilterNotFound < Error; end + + # InternalError means that you hit a bug of Haml itself. + class InternalError < Error; end end diff --git a/lib/haml/haml_error.rb b/lib/haml/haml_error.rb deleted file mode 100644 index a9441f4e..00000000 --- a/lib/haml/haml_error.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -module Haml - # An exception raised by Haml code. - # TODO: unify Haml::Error (former Hamlit::Error) and Haml::HamlError (former Haml::Error) - class HamlError < StandardError - - MESSAGES = { - bad_script_indent: '"%s" is indented at wrong level: expected %d, but was at %d.', - cant_run_filter: 'Can\'t run "%s" filter; you must require its dependencies first', - cant_use_tabs_and_spaces: "Indentation can't use both tabs and spaces.", - deeper_indenting: "The line was indented %d levels deeper than the previous line.", - filter_not_defined: 'Filter "%s" is not defined.', - gem_install_filter_deps: '"%s" filter\'s %s dependency missing: try installing it or adding it to your Gemfile', - illegal_element: "Illegal element: classes and ids must have values.", - illegal_nesting_content: "Illegal nesting: nesting within a tag that already has content is illegal.", - illegal_nesting_header: "Illegal nesting: nesting within a header command is illegal.", - illegal_nesting_line: "Illegal nesting: content can't be both given on the same line as %%%s and nested within it.", - illegal_nesting_plain: "Illegal nesting: nesting within plain text is illegal.", - illegal_nesting_self_closing: "Illegal nesting: nesting within a self-closing tag is illegal.", - inconsistent_indentation: "Inconsistent indentation: %s used for indentation, but the rest of the document was indented using %s.", - indenting_at_start: "Indenting at the beginning of the document is illegal.", - install_haml_contrib: 'To use the "%s" filter, please install the haml-contrib gem.', - invalid_attribute_list: 'Invalid attribute list: %s.', - invalid_filter_name: 'Invalid filter name ":%s".', - invalid_tag: 'Invalid tag: "%s".', - missing_if: 'Got "%s" with no preceding "if"', - no_ruby_code: "There's no Ruby code for %s to evaluate.", - self_closing_content: "Self-closing tags can't have content.", - unbalanced_brackets: 'Unbalanced brackets.', - no_end: <<-END -You don't need to use "- end" in Haml. Un-indent to close a block: -- if foo? - %strong Foo! -- else - Not foo. -%p This line is un-indented, so it isn't part of the "if" block -END - }.freeze - - def self.message(key, *args) - string = MESSAGES[key] or raise "[HAML BUG] No error messages for #{key}" - (args.empty? ? string : string % args).rstrip - end - - # The line of the template on which the error occurred. - # - # @return [Fixnum] - attr_reader :line - - # @param message [String] The error message - # @param line [Fixnum] See \{#line} - def initialize(message = nil, line = nil) - super(message) - @line = line - end - end - - # SyntaxError is the type of exception raised when Haml encounters an - # ill-formatted document. - # It's not particularly interesting, - # except in that it's a subclass of {Haml::HamlError}. - class HamlSyntaxError < HamlError; end - - class HamlInvalidAttributeNameError < HamlSyntaxError; end -end diff --git a/lib/haml/parser.rb b/lib/haml/parser.rb index 0a674cec..51f5d179 100644 --- a/lib/haml/parser.rb +++ b/lib/haml/parser.rb @@ -2,7 +2,7 @@ require 'ripper' require 'strscan' -require 'haml/haml_error' +require 'haml/error' require 'haml/util' module Haml @@ -125,7 +125,7 @@ module Haml @indentation = nil @line = next_line - raise HamlSyntaxError.new(HamlError.message(:indenting_at_start), @line.index) if @line.tabs != 0 + raise SyntaxError.new(Error.message(:indenting_at_start), @line.index) if @line.tabs != 0 loop do next_line @@ -148,7 +148,7 @@ module Haml end if !flat? && @next_line.tabs - @line.tabs > 1 - raise HamlSyntaxError.new(HamlError.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index) + raise SyntaxError.new(Error.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index) end @line = @next_line @@ -156,7 +156,7 @@ module Haml # Close all the open tags close until @parent.type == :root @root - rescue Haml::HamlError => e + rescue Haml::Error => e e.backtrace.unshift "#{@options.filename}:#{(e.line ? e.line + 1 : @line.index + 1) + @options.line - 1}" error_with_lineno(e) end @@ -168,7 +168,7 @@ module Haml @indentation = line.whitespace if @indentation.include?(?\s) && @indentation.include?(?\t) - raise HamlSyntaxError.new(HamlError.message(:cant_use_tabs_and_spaces), line.index) + raise SyntaxError.new(Error.message(:cant_use_tabs_and_spaces), line.index) end @flat_spaces = @indentation * (@template_tabs+1) if flat? @@ -179,11 +179,11 @@ module Haml return tabs if line.whitespace == @indentation * tabs return @template_tabs + 1 if flat? && line.whitespace =~ /^#{@flat_spaces}/ - message = HamlError.message(:inconsistent_indentation, + message = Error.message(:inconsistent_indentation, human_indentation(line.whitespace), human_indentation(@indentation) ) - raise HamlSyntaxError.new(message, line.index) + raise SyntaxError.new(message, line.index) end private @@ -195,7 +195,7 @@ module Haml return error unless trace line = trace.match(/\d+\z/).to_s.to_i - HamlSyntaxError.new(error.message, line) + SyntaxError.new(error.message, line) end # @private @@ -320,7 +320,7 @@ module Haml def plain(line, escape_html = nil) if block_opened? - raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_plain), @next_line.index) + raise SyntaxError.new(Error.message(:illegal_nesting_plain), @next_line.index) end unless Util.contains_interpolation?(line.text) @@ -333,7 +333,7 @@ module Haml end def script(line, escape_html = nil, preserve = false) - raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, '=')) if line.text.empty? + raise SyntaxError.new(Error.message(:no_ruby_code, '=')) if line.text.empty? line = handle_ruby_multiline(line) escape_html = @options.escape_html if escape_html.nil? @@ -345,12 +345,12 @@ module Haml end def flat_script(line, escape_html = nil) - raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, '~')) if line.text.empty? + raise SyntaxError.new(Error.message(:no_ruby_code, '~')) if line.text.empty? script(line, escape_html, :preserve) end def silent_script(line) - raise HamlSyntaxError.new(HamlError.message(:no_end), line.index) if line.text[1..-1].strip == 'end' + raise SyntaxError.new(Error.message(:no_end), line.index) if line.text[1..-1].strip == 'end' line = handle_ruby_multiline(line) keyword = block_keyword(line.text) @@ -359,7 +359,7 @@ module Haml if ["else", "elsif", "when"].include?(keyword) if @script_level_stack.empty? - raise Haml::HamlSyntaxError.new(HamlError.message(:missing_if, keyword), @line.index) + raise Haml::SyntaxError.new(Error.message(:missing_if, keyword), @line.index) end if keyword == 'when' and !@script_level_stack.last[2] @@ -370,8 +370,8 @@ module Haml end if @script_level_stack.last[1] != @line.tabs - message = HamlError.message(:bad_script_indent, keyword, @script_level_stack.last[1], @line.tabs) - raise Haml::HamlSyntaxError.new(message, @line.index) + message = Error.message(:bad_script_indent, keyword, @script_level_stack.last[1], @line.tabs) + raise Haml::SyntaxError.new(message, @line.index) end end @@ -460,12 +460,12 @@ module Haml dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval end - raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing - raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, action), last_line - 1) if parse && value.empty? - raise HamlSyntaxError.new(HamlError.message(:self_closing_content), last_line - 1) if self_closing && !value.empty? + raise SyntaxError.new(Error.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing + raise SyntaxError.new(Error.message(:no_ruby_code, action), last_line - 1) if parse && value.empty? + raise SyntaxError.new(Error.message(:self_closing_content), last_line - 1) if self_closing && !value.empty? if block_opened? && !value.empty? && !is_ruby_multiline?(value) - raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_line, tag_name), @next_line.index) + raise SyntaxError.new(Error.message(:illegal_nesting_line, tag_name), @next_line.index) end self_closing ||= !!(!block_opened? && value.empty? && @options.autoclose.any? {|t| t === tag_name}) @@ -509,7 +509,7 @@ module Haml end if block_opened? && !text.empty? - raise HamlSyntaxError.new(Haml::HamlError.message(:illegal_nesting_content), @next_line.index) + raise SyntaxError.new(Haml::Error.message(:illegal_nesting_content), @next_line.index) end ParseNode.new(:comment, @line.index + 1, :conditional => conditional, :text => text, :revealed => revealed, :parse => parse) @@ -517,13 +517,13 @@ module Haml # Renders an XHTML doctype or XML shebang. def doctype(text) - raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_header), @next_line.index) if block_opened? + raise SyntaxError.new(Error.message(:illegal_nesting_header), @next_line.index) if block_opened? version, type, encoding = text[3..-1].strip.downcase.scan(DOCTYPE_REGEX)[0] ParseNode.new(:doctype, @line.index + 1, :version => version, :type => type, :encoding => encoding) end def filter(name) - raise HamlError.new(HamlError.message(:invalid_filter_name, name)) unless name =~ /^\w+$/ + raise Error.new(Error.message(:invalid_filter_name, name)) unless name =~ /^\w+$/ if filter_opened? @flat = true @@ -621,12 +621,12 @@ module Haml # Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value def parse_tag(text) match = text.scan(/%([-:\w]+)([-:\w.#\@]*)(.+)?/)[0] - raise HamlSyntaxError.new(HamlError.message(:invalid_tag, text)) unless match + raise SyntaxError.new(Error.message(:invalid_tag, text)) unless match tag_name, attributes, rest = match if !attributes.empty? && (attributes =~ /[.#](\.|#|\z)/) - raise HamlSyntaxError.new(HamlError.message(:illegal_element)) + raise SyntaxError.new(Error.message(:illegal_element)) end new_attributes_hash = old_attributes_hash = last_line = nil @@ -685,8 +685,8 @@ module Haml # 1 more :on_embexpr_end (the last '}') than :on_embexpr_beg, and resurrects '{' afterwards. balanced, rest = balance_tokens(text.sub(?{, METHOD_CALL_PREFIX), :on_embexpr_beg, :on_embexpr_end, count: 1) attributes_hash = balanced.sub(METHOD_CALL_PREFIX, ?{) - rescue HamlSyntaxError => e - if e.message == HamlError.message(:unbalanced_brackets) && !@template.empty? + rescue SyntaxError => e + if e.message == Error.message(:unbalanced_brackets) && !@template.empty? text << "\n#{@next_line.text}" last_line += 1 next_line @@ -715,7 +715,7 @@ module Haml if name == false scanned = Haml::Util.balance(text, ?(, ?)) text = scanned ? scanned.first : text - raise Haml::HamlSyntaxError.new(HamlError.message(:invalid_attribute_list, text.inspect), last_line - 1) + raise Haml::SyntaxError.new(Error.message(:invalid_attribute_list, text.inspect), last_line - 1) end attributes[name] = value scanner.scan(/\s*/) @@ -837,7 +837,7 @@ module Haml end def balance(*args) - Haml::Util.balance(*args) or raise(HamlSyntaxError.new(HamlError.message(:unbalanced_brackets))) + Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets))) end # Unlike #balance, this balances Ripper tokens to balance something like `{ a: "}" }` correctly. @@ -856,7 +856,7 @@ module Haml return text, buf.sub(text, '') end end - raise HamlSyntaxError.new(HamlError.message(:unbalanced_brackets)) + raise SyntaxError.new(Error.message(:unbalanced_brackets)) end def block_opened? diff --git a/test/hamlit/error_test.rb b/test/hamlit/error_test.rb index 74d35454..17e99d4f 100644 --- a/test/hamlit/error_test.rb +++ b/test/hamlit/error_test.rb @@ -1,8 +1,8 @@ describe Haml::Engine do - describe 'HamlSyntaxError' do + describe 'SyntaxError' do it 'raises on runtime' do code = Haml::Engine.new.call(" %a") - assert_raises(Haml::HamlSyntaxError) do + assert_raises(Haml::SyntaxError) do eval code end end @@ -11,7 +11,7 @@ describe Haml::Engine do code = Haml::Engine.new.call("\n\n %a") begin eval code - rescue Haml::HamlSyntaxError => e + rescue Haml::SyntaxError => e assert_equal(2, e.line) end end @@ -27,7 +27,7 @@ describe Haml::Engine do HAML begin eval code - rescue Haml::HamlSyntaxError => e + rescue Haml::SyntaxError => e assert_equal(3, e.line) end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 57aebf58..e3e516bb 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -100,6 +100,6 @@ class Haml::TestCase < BASE_TEST_CLASS end def self.error(*args) - Haml::HamlError.message(*args) + Haml::Error.message(*args) end end