From 3dae5aea8a976378568cd3b8025708a5c8fa3486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 16 Aug 2022 01:02:45 +0000 Subject: [PATCH] Apply the Shopify styleguide --- .github/workflows/ruby.yml | 21 + .rubocop.yml | 9 + Gemfile | 18 +- Gemfile.lock | 28 +- Rakefile | 29 +- better_html.gemspec | 22 +- ext/better_html_ext/extconf.rb | 10 +- gemfiles/Gemfile-rails-6-0 | 16 +- gemfiles/Gemfile-rails-6-1 | 16 +- lib/better_html.rb | 36 +- lib/better_html/ast/iterator.rb | 23 +- lib/better_html/ast/node.rb | 6 +- lib/better_html/better_erb.rb | 109 +- .../better_erb/erubi_implementation.rb | 82 +- lib/better_html/better_erb/runtime_checks.rb | 272 ++--- .../better_erb/validated_output_buffer.rb | 52 +- lib/better_html/config.rb | 10 +- lib/better_html/errors.rb | 6 +- lib/better_html/helpers.rb | 10 +- lib/better_html/html_attributes.rb | 8 +- lib/better_html/parser.rb | 35 +- lib/better_html/railtie.rb | 12 +- lib/better_html/test_helper/ruby_node.rb | 25 +- .../safe_erb/allowed_script_type.rb | 12 +- lib/better_html/test_helper/safe_erb/base.rb | 21 +- .../safe_erb/no_javascript_tag_helper.rb | 10 +- .../test_helper/safe_erb/no_statements.rb | 10 +- .../safe_erb/script_interpolation.rb | 13 +- .../test_helper/safe_erb/tag_interpolation.rb | 43 +- .../test_helper/safe_erb_tester.rb | 64 +- .../test_helper/safe_lodash_tester.rb | 71 +- lib/better_html/test_helper/safety_error.rb | 2 + lib/better_html/tokenizer/base_erb.rb | 22 +- lib/better_html/tokenizer/html_erb.rb | 4 +- lib/better_html/tokenizer/html_lodash.rb | 35 +- lib/better_html/tokenizer/javascript_erb.rb | 4 +- lib/better_html/tokenizer/location.rb | 23 +- lib/better_html/tokenizer/token.rb | 2 + lib/better_html/tokenizer/token_array.rb | 16 +- lib/better_html/tree/attribute.rb | 16 +- lib/better_html/tree/attributes_list.rb | 14 +- lib/better_html/tree/tag.rb | 16 +- lib/better_html/version.rb | 2 + lib/tasks/better_html_tasks.rake | 1 + .../better_erb/implementation_test.rb | 836 ++++++------- test/better_html/errors_test.rb | 6 +- test/better_html/helpers_test.rb | 76 +- test/better_html/parser_test.rb | 112 +- .../better_html/test_helper/ruby_node_test.rb | 32 +- .../safe_erb/allowed_script_type_test.rb | 16 +- .../safe_erb/no_javascript_tag_helper_test.rb | 13 +- .../safe_erb/no_statements_test.rb | 13 +- .../safe_erb/script_interpolation_test.rb | 19 +- .../safe_erb/tag_interpolation_test.rb | 108 +- .../test_helper/safe_lodash_tester_test.rb | 17 +- test/better_html/tokenizer/html_erb_test.rb | 91 +- .../better_html/tokenizer/html_lodash_test.rb | 22 +- test/better_html/tokenizer/location_test.rb | 6 +- .../better_html/tokenizer/token_array_test.rb | 16 +- test/better_html/tokenizer/token_test.rb | 8 +- test/dummy/Rakefile | 4 +- .../app/controllers/application_controller.rb | 2 + test/dummy/app/helpers/application_helper.rb | 2 + test/dummy/bin/bundle | 6 +- test/dummy/bin/rails | 8 +- test/dummy/bin/rake | 6 +- test/dummy/bin/setup | 8 +- test/dummy/config.ru | 4 +- test/dummy/config/application.rb | 7 +- test/dummy/config/boot.rb | 8 +- test/dummy/config/environment.rb | 4 +- test/dummy/config/environments/development.rb | 2 + test/dummy/config/environments/production.rb | 4 +- test/dummy/config/environments/test.rb | 4 +- test/dummy/config/initializers/assets.rb | 4 +- .../initializers/backtrace_silencers.rb | 1 + .../config/initializers/cookies_serializer.rb | 2 + .../initializers/filter_parameter_logging.rb | 2 + test/dummy/config/initializers/inflections.rb | 1 + test/dummy/config/initializers/mime_types.rb | 1 + .../config/initializers/session_store.rb | 4 +- .../config/initializers/wrap_parameters.rb | 2 + test/dummy/config/routes.rb | 2 + test/html_tokenizer/parser_test.rb | 1034 +++++++++-------- test/html_tokenizer/tokenizer_test.rb | 708 ++++++----- test/test_helper.rb | 22 +- 86 files changed, 2459 insertions(+), 2040 deletions(-) create mode 100644 .rubocop.yml diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index b218731..231aaa2 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -32,3 +32,24 @@ jobs: bundler-cache: true - name: Run tests run: bundle exec rake + lint: + runs-on: ubuntu-latest + name: Lint + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1 + bundler-cache: true + - name: Run style checks + run: bundle exec rubocop + buildall: + if: ${{ always() }} + runs-on: ubuntu-latest + name: Build (matrix) + needs: [lint, test] + steps: + - name: Check build matrix status + if: ${{ needs.test.result != 'success' || needs.lint.result != 'success' }} + run: exit 1 diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..23b1ed2 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,9 @@ +# This file strictly follows the rules defined in the Ruby style guide: +# http://shopify.github.io/ruby-style-guide/ +inherit_gem: + rubocop-shopify: rubocop.yml + +AllCops: + TargetRubyVersion: 2.7 + NewCops: disable + SuggestExtensions: false diff --git a/Gemfile b/Gemfile index 73973af..cf23ea4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,13 +1,17 @@ -source 'https://rubygems.org' +# frozen_string_literal: true + +source "https://rubygems.org" gemspec -gem 'actionview' -gem 'rake' -gem 'rake-compiler' -gem 'minitest' -gem 'mocha' +gem "actionview" +gem "minitest" +gem "mocha" +gem "rake" +gem "rake-compiler" group :deployment, :test do - gem 'pry-byebug' + gem "pry-byebug" end + +gem "rubocop-shopify" diff --git a/Gemfile.lock b/Gemfile.lock index dbdc9f9..01a2f1f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,7 +23,7 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - ast (2.4.0) + ast (2.4.2) builder (3.2.4) byebug (9.1.0) coderay (1.1.2) @@ -32,6 +32,7 @@ GEM erubi (1.11.0) i18n (1.12.0) concurrent-ruby (~> 1.0) + json (2.6.2) loofah (2.18.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -42,8 +43,9 @@ GEM nokogiri (1.13.8) mini_portile2 (~> 2.8.0) racc (~> 1.4) - parser (2.6.3.0) - ast (~> 2.4.0) + parallel (1.22.1) + parser (3.1.2.1) + ast (~> 2.4.1) pry (0.11.2) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -56,12 +58,31 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.4.3) loofah (~> 2.3) + rainbow (3.1.1) rake (13.0.6) rake-compiler (1.2.0) rake + regexp_parser (2.5.0) + rexml (3.2.5) + rubocop (1.35.0) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.1.2.1) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.20.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.21.0) + parser (>= 3.1.1.0) + rubocop-shopify (2.9.0) + rubocop (~> 1.33) + ruby-progressbar (1.11.0) smart_properties (1.15.0) tzinfo (2.0.5) concurrent-ruby (~> 1.0) + unicode-display_width (2.2.0) PLATFORMS ruby @@ -74,6 +95,7 @@ DEPENDENCIES pry-byebug rake rake-compiler + rubocop-shopify BUNDLED WITH 2.2.22 diff --git a/Rakefile b/Rakefile index 70975f4..5b30e52 100644 --- a/Rakefile +++ b/Rakefile @@ -1,32 +1,33 @@ +# frozen_string_literal: true + begin - require 'bundler/setup' + require "bundler/setup" require "bundler/gem_tasks" rescue LoadError - puts 'You must `gem install bundler` and `bundle install` to run rake tasks' + puts "You must `gem install bundler` and `bundle install` to run rake tasks" end -require 'rake/extensiontask' +require "rake/extensiontask" Rake::ExtensionTask.new("better_html_ext") -require 'rdoc/task' +require "rdoc/task" RDoc::Task.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'BetterHtml' - rdoc.options << '--line-numbers' - rdoc.rdoc_files.include('README.rdoc') - rdoc.rdoc_files.include('lib/**/*.rb') + rdoc.rdoc_dir = "rdoc" + rdoc.title = "BetterHtml" + rdoc.options << "--line-numbers" + rdoc.rdoc_files.include("README.rdoc") + rdoc.rdoc_files.include("lib/**/*.rb") end -require 'rake/testtask' +require "rake/testtask" Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.libs << 'test' - t.pattern = 'test/**/*_test.rb' + t.libs << "lib" + t.libs << "test" + t.pattern = "test/**/*_test.rb" t.verbose = false end - task default: [:compile, :test] diff --git a/better_html.gemspec b/better_html.gemspec index 547cd08..c7564be 100644 --- a/better_html.gemspec +++ b/better_html.gemspec @@ -1,4 +1,6 @@ -$:.push File.expand_path("../lib", __FILE__) +# frozen_string_literal: true + +$LOAD_PATH.push(File.expand_path("../lib", __FILE__)) # Maintain your gem's version: require "better_html/version" @@ -20,19 +22,19 @@ Gem::Specification.new do |s| "bug_tracker_uri" => "https://github.com/Shopify/better-html/issues", "changelog_uri" => "https://github.com/Shopify/better-html/releases", "source_code_uri" => "https://github.com/Shopify/better-html/tree/v#{s.version}", - "allowed_push_host" => "https://rubygems.org" + "allowed_push_host" => "https://rubygems.org", } - s.extensions = ['ext/better_html_ext/extconf.rb'] + s.extensions = ["ext/better_html_ext/extconf.rb"] s.files = Dir["{app,config,db,lib,ext}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] s.require_paths = ["lib"] - s.add_dependency 'ast', '~> 2.0' - s.add_dependency 'erubi', '~> 1.4' - s.add_dependency 'activesupport', '>= 6.0' - s.add_dependency 'actionview', '>= 6.0' - s.add_dependency 'parser', '>= 2.4' - s.add_dependency 'smart_properties' + s.add_dependency("actionview", ">= 6.0") + s.add_dependency("activesupport", ">= 6.0") + s.add_dependency("ast", "~> 2.0") + s.add_dependency("erubi", "~> 1.4") + s.add_dependency("parser", ">= 2.4") + s.add_dependency("smart_properties") - s.add_development_dependency 'rake', '~> 13' + s.add_development_dependency("rake", "~> 13") end diff --git a/ext/better_html_ext/extconf.rb b/ext/better_html_ext/extconf.rb index 7e7cc40..7bb4755 100644 --- a/ext/better_html_ext/extconf.rb +++ b/ext/better_html_ext/extconf.rb @@ -1,12 +1,16 @@ -require 'mkmf' +# frozen_string_literal: true +require "mkmf" + +# rubocop:disable Style/GlobalVars $CXXFLAGS += " -std=c++11 " $CXXFLAGS += " -g -O1 -ggdb " $CFLAGS += " -g -O1 -ggdb " -if ENV['DEBUG'] +if ENV["DEBUG"] $CXXFLAGS += " -DDEBUG " $CFLAGS += " -DDEBUG " end +# rubocop:enable Style/GlobalVars -create_makefile('better_html_ext') +create_makefile("better_html_ext") diff --git a/gemfiles/Gemfile-rails-6-0 b/gemfiles/Gemfile-rails-6-0 index d6ef028..f20ce95 100644 --- a/gemfiles/Gemfile-rails-6-0 +++ b/gemfiles/Gemfile-rails-6-0 @@ -1,13 +1,15 @@ -source 'https://rubygems.org' +# frozen_string_literal: true + +source "https://rubygems.org" gemspec path: ".." -gem 'actionview', '~> 6.0.0' -gem 'rake' -gem 'rake-compiler' -gem 'minitest' -gem 'mocha' +gem "actionview", "~> 6.0.0" +gem "rake" +gem "rake-compiler" +gem "minitest" +gem "mocha" group :deployment, :test do - gem 'pry-byebug' + gem "pry-byebug" end diff --git a/gemfiles/Gemfile-rails-6-1 b/gemfiles/Gemfile-rails-6-1 index 27c7be6..3397a95 100644 --- a/gemfiles/Gemfile-rails-6-1 +++ b/gemfiles/Gemfile-rails-6-1 @@ -1,13 +1,15 @@ -source 'https://rubygems.org' +# frozen_string_literal: true + +source "https://rubygems.org" gemspec path: ".." -gem 'actionview', '~> 6.1.0' -gem 'rake' -gem 'rake-compiler' -gem 'minitest' -gem 'mocha' +gem "actionview", "~> 6.1.0" +gem "rake" +gem "rake-compiler" +gem "minitest" +gem "mocha" group :deployment, :test do - gem 'pry-byebug' + gem "pry-byebug" end diff --git a/lib/better_html.rb b/lib/better_html.rb index 682598f..0725fc3 100644 --- a/lib/better_html.rb +++ b/lib/better_html.rb @@ -1,25 +1,27 @@ -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/class/attribute_accessors' +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/class/attribute_accessors" module BetterHtml - def self.configure - yield config if block_given? - end + class << self + attr_writer :config - def self.config - @config ||= Config.new - end + def config + @config ||= Config.new + end - def self.config=(new_config) - @config = new_config + def configure + yield config if block_given? + end end end -require 'better_html/version' -require 'better_html/config' -require 'better_html/helpers' -require 'better_html/errors' -require 'better_html/html_attributes' +require "better_html/version" +require "better_html/config" +require "better_html/helpers" +require "better_html/errors" +require "better_html/html_attributes" -require 'better_html/railtie' if defined?(Rails) +require "better_html/railtie" if defined?(Rails) diff --git a/lib/better_html/ast/iterator.rb b/lib/better_html/ast/iterator.rb index 808ab07..afc0df8 100644 --- a/lib/better_html/ast/iterator.rb +++ b/lib/better_html/ast/iterator.rb @@ -1,9 +1,20 @@ -require 'ast' -require 'active_support/core_ext/array/wrap' +# frozen_string_literal: true + +require "ast" +require "active_support/core_ext/array/wrap" module BetterHtml module AST class Iterator + class << self + def descendants(root_node, type) + Enumerator.new do |yielder| + t = new(type) { |node| yielder << node } + t.traverse(root_node) + end + end + end + def initialize(types, &block) @types = types.nil? ? nil : Array.wrap(types) @block = block @@ -11,6 +22,7 @@ def initialize(types, &block) def traverse(node) return unless node.is_a?(::AST::Node) + @block.call(node) if @types.nil? || @types.include?(node.type) traverse_all(node) end @@ -20,13 +32,6 @@ def traverse_all(nodes) traverse(node) if node.is_a?(::AST::Node) end end - - def self.descendants(root_node, type) - Enumerator.new do |yielder| - t = new(type) { |node| yielder << node } - t.traverse(root_node) - end - end end end end diff --git a/lib/better_html/ast/node.rb b/lib/better_html/ast/node.rb index 6659e5d..102abe3 100644 --- a/lib/better_html/ast/node.rb +++ b/lib/better_html/ast/node.rb @@ -1,5 +1,7 @@ -require 'ast' -require_relative 'iterator' +# frozen_string_literal: true + +require "ast" +require_relative "iterator" module BetterHtml module AST diff --git a/lib/better_html/better_erb.rb b/lib/better_html/better_erb.rb index ca124bd..9a2e394 100644 --- a/lib/better_html/better_erb.rb +++ b/lib/better_html/better_erb.rb @@ -1,8 +1,10 @@ -require 'action_view' +# frozen_string_literal: true -require 'better_html_ext' -require 'better_html/better_erb/erubi_implementation' -require 'better_html/better_erb/validated_output_buffer' +require "action_view" + +require "better_html_ext" +require "better_html/better_erb/erubi_implementation" +require "better_html/better_erb/validated_output_buffer" module BetterHtml class ParserError < RuntimeError @@ -17,59 +19,60 @@ def initialize(message, position, line, column) end end -class BetterHtml::BetterErb - cattr_accessor :content_types - - self.content_types = { - 'html.erb' => BetterHtml::BetterErb::ErubiImplementation - } - - def self.prepend! - ActionView::Template::Handlers::ERB.prepend(ConditionalImplementation) - end - - private +module BetterHtml + class BetterErb + cattr_accessor :content_types - module ConditionalImplementation + self.content_types = { + "html.erb" => BetterHtml::BetterErb::ErubiImplementation, + } - def call(template, source = nil) - generate(template, source) + class << self + def prepend! + ActionView::Template::Handlers::ERB.prepend(ConditionalImplementation) + end end - private - - def generate(template, source) - # First, convert to BINARY, so in case the encoding is - # wrong, we can still find an encoding tag - # (<%# encoding %>) inside the String using a regular - # expression - - source ||= template.source - filename = template.identifier.split("/").last - exts = filename.split(".") - exts = exts[1..exts.length].join(".") - template_source = source.dup.force_encoding(Encoding::ASCII_8BIT) - - erb = template_source.gsub(ActionView::Template::Handlers::ERB::ENCODING_TAG, '') - encoding = $2 - - erb.force_encoding valid_encoding(source.dup, encoding) - - # Always make sure we return a String in the default_internal - erb.encode! - - excluded_template = !!BetterHtml.config.template_exclusion_filter&.call(template.identifier) - klass = BetterHtml::BetterErb.content_types[exts] unless excluded_template - klass ||= self.class.erb_implementation - - escape = self.class.escape_ignore_list.include?(template.type) - generator = klass.new( - erb, - :escape => escape, - :trim => (self.class.erb_trim_mode == "-") - ) - generator.validate! if generator.respond_to?(:validate!) - generator.src + module ConditionalImplementation + def call(template, source = nil) + generate(template, source) + end + + private + + def generate(template, source) + # First, convert to BINARY, so in case the encoding is + # wrong, we can still find an encoding tag + # (<%# encoding %>) inside the String using a regular + # expression + + source ||= template.source + filename = template.identifier.split("/").last + exts = filename.split(".") + exts = exts[1..exts.length].join(".") + template_source = source.dup.force_encoding(Encoding::ASCII_8BIT) + + erb = template_source.gsub(ActionView::Template::Handlers::ERB::ENCODING_TAG, "") + encoding = Regexp.last_match(2) + + erb.force_encoding(valid_encoding(source.dup, encoding)) + + # Always make sure we return a String in the default_internal + erb.encode! + + excluded_template = !!BetterHtml.config.template_exclusion_filter&.call(template.identifier) + klass = BetterHtml::BetterErb.content_types[exts] unless excluded_template + klass ||= self.class.erb_implementation + + escape = self.class.escape_ignore_list.include?(template.type) + generator = klass.new( + erb, + escape: escape, + trim: (self.class.erb_trim_mode == "-") + ) + generator.validate! if generator.respond_to?(:validate!) + generator.src + end end end end diff --git a/lib/better_html/better_erb/erubi_implementation.rb b/lib/better_html/better_erb/erubi_implementation.rb index 47169c9..ca56922 100644 --- a/lib/better_html/better_erb/erubi_implementation.rb +++ b/lib/better_html/better_erb/erubi_implementation.rb @@ -1,50 +1,54 @@ -require 'action_view' -require_relative 'runtime_checks' - -class BetterHtml::BetterErb - class ErubiImplementation < ActionView::Template::Handlers::ERB::Erubi - include RuntimeChecks - - def add_text(text) - return if text.empty? - - if text == "\n" - @parser.parse("\n") - @newline_pending += 1 - else - src << "@output_buffer.safe_append='" - src << "\n" * @newline_pending if @newline_pending > 0 - src << escape_text(text) - src << "'.freeze;" - - @parser.parse(text) do |*args| - check_token(*args) - end +# frozen_string_literal: true + +require "action_view" +require_relative "runtime_checks" + +module BetterHtml + class BetterErb + class ErubiImplementation < ActionView::Template::Handlers::ERB::Erubi + include RuntimeChecks + + def add_text(text) + return if text.empty? - @newline_pending = 0 + if text == "\n" + @parser.parse("\n") + @newline_pending += 1 + else + src << "@output_buffer.safe_append='" + src << "\n" * @newline_pending if @newline_pending > 0 + src << escape_text(text) + src << "'.freeze;" + + @parser.parse(text) do |*args| + check_token(*args) + end + + @newline_pending = 0 + end end - end - def add_expression(indicator, code) - if (indicator == "==") || @escape - add_expr_auto_escaped(src, code, false) - else - add_expr_auto_escaped(src, code, true) + def add_expression(indicator, code) + if (indicator == "==") || @escape + add_expr_auto_escaped(src, code, false) + else + add_expr_auto_escaped(src, code, true) + end end - end - def add_code(code) - flush_newline_if_pending(src) + def add_code(code) + flush_newline_if_pending(src) - block_check(src, "<%#{code}%>") - @parser.append_placeholder(code) - super - end + block_check(src, "<%#{code}%>") + @parser.append_placeholder(code) + super + end - private + private - def escape_text(text) - text.gsub(/['\\]/, '\\\\\&') + def escape_text(text) + text.gsub(/['\\]/, '\\\\\&') + end end end end diff --git a/lib/better_html/better_erb/runtime_checks.rb b/lib/better_html/better_erb/runtime_checks.rb index 6bd6b70..1f2dccd 100644 --- a/lib/better_html/better_erb/runtime_checks.rb +++ b/lib/better_html/better_erb/runtime_checks.rb @@ -1,161 +1,169 @@ -require 'action_view' - -class BetterHtml::BetterErb - module RuntimeChecks - def initialize(erb, config: BetterHtml.config, **options) - @parser = HtmlTokenizer::Parser.new - @config = config - super(erb, **options) - end +# frozen_string_literal: true - def validate! - check_parser_errors unless @config.disable_parser_validation +require "action_view" - unless @parser.context == :none - raise BetterHtml::HtmlError, 'Detected an open tag at the end of this document.' +module BetterHtml + class BetterErb + module RuntimeChecks + def initialize(erb, config: BetterHtml.config, **options) + @parser = HtmlTokenizer::Parser.new + @config = config + super(erb, **options) end - end - private + def validate! + check_parser_errors unless @config.disable_parser_validation - def class_name - "BetterHtml::BetterErb::ValidatedOutputBuffer" - end + unless @parser.context == :none + raise BetterHtml::HtmlError, "Detected an open tag at the end of this document." + end + end - def wrap_method - "#{class_name}.wrap" - end + private - def add_expr_auto_escaped(src, code, auto_escape) - flush_newline_if_pending(src) + def class_name + "BetterHtml::BetterErb::ValidatedOutputBuffer" + end - src << "#{wrap_method}(@output_buffer, (#{parser_context.inspect}), '#{escape_text(code)}'.freeze, #{auto_escape})" - method_name = "safe_#{@parser.context}_append" - if code =~ self.class::BLOCK_EXPR - block_check(src, "<%=#{code}%>") - src << ".#{method_name}= " << code - else - src << ".#{method_name}=(" << code << ");" + def wrap_method + "#{class_name}.wrap" end - @parser.append_placeholder("<%=#{code}%>") - end - def parser_context - if [:quoted_value, :unquoted_value, :space_after_attribute].include?(@parser.context) - { - tag_name: @parser.tag_name, - attribute_name: @parser.attribute_name, - attribute_value: @parser.attribute_value, - attribute_quoted: @parser.attribute_quoted?, - quote_character: @parser.quote_character, - } - elsif [:attribute_name, :after_attribute_name, :after_equal].include?(@parser.context) - { - tag_name: @parser.tag_name, - attribute_name: @parser.attribute_name, - } - elsif [:tag, :tag_name, :tag_end].include?(@parser.context) - { - tag_name: @parser.tag_name, - } - elsif @parser.context == :rawtext - { - tag_name: @parser.tag_name, - rawtext_text: @parser.rawtext_text, - } - elsif @parser.context == :comment - { - comment_text: @parser.comment_text, - } - elsif [:none, :solidus_or_tag_name].include?(@parser.context) - {} - else - raise RuntimeError, "Tried to interpolate into unknown location #{@parser.context}." + def add_expr_auto_escaped(src, code, auto_escape) + flush_newline_if_pending(src) + + escaped_code = escape_text(code) + + src << "#{wrap_method}(@output_buffer, (#{parser_context.inspect}), '#{escaped_code}'.freeze, #{auto_escape})" + method_name = "safe_#{@parser.context}_append" + if code =~ self.class::BLOCK_EXPR + block_check(src, "<%=#{code}%>") + src << ".#{method_name}= " << code + else + src << ".#{method_name}=(" << code << ");" + end + @parser.append_placeholder("<%=#{code}%>") end - end - def block_check(src, code) - unless @parser.context == :none || @parser.context == :rawtext - s = "Ruby statement not allowed.\n" - s << "In '#{@parser.context}' on line #{@parser.line_number} column #{@parser.column_number}:\n" - prefix = extract_line(@parser.line_number) - code = code.lines.first - s << "#{prefix}#{code}\n" - s << "#{' ' * prefix.size}#{'^' * code.size}" - raise BetterHtml::DontInterpolateHere, s + def parser_context + if [:quoted_value, :unquoted_value, :space_after_attribute].include?(@parser.context) + { + tag_name: @parser.tag_name, + attribute_name: @parser.attribute_name, + attribute_value: @parser.attribute_value, + attribute_quoted: @parser.attribute_quoted?, + quote_character: @parser.quote_character, + } + elsif [:attribute_name, :after_attribute_name, :after_equal].include?(@parser.context) + { + tag_name: @parser.tag_name, + attribute_name: @parser.attribute_name, + } + elsif [:tag, :tag_name, :tag_end].include?(@parser.context) + { + tag_name: @parser.tag_name, + } + elsif @parser.context == :rawtext + { + tag_name: @parser.tag_name, + rawtext_text: @parser.rawtext_text, + } + elsif @parser.context == :comment + { + comment_text: @parser.comment_text, + } + elsif [:none, :solidus_or_tag_name].include?(@parser.context) + {} + else + raise "Tried to interpolate into unknown location #{@parser.context}." + end end - end - def check_parser_errors - errors = @parser.errors - return if errors.empty? - - s = "#{errors.size} error(s) found in HTML document.\n" - errors.each do |error| - s = "#{error.message}\n" - s << "On line #{error.line} column #{error.column}:\n" - line = extract_line(error.line) - s << "#{line}\n" - s << "#{' ' * (error.column)}#{'^' * (line.size - error.column)}" + def block_check(src, code) + unless @parser.context == :none || @parser.context == :rawtext + s = +"Ruby statement not allowed.\n" + s << "In '#{@parser.context}' on line #{@parser.line_number} column #{@parser.column_number}:\n" + prefix = extract_line(@parser.line_number) + code = code.lines.first + s << "#{prefix}#{code}\n" + s << "#{" " * prefix.size}#{"^" * code.size}" + raise BetterHtml::DontInterpolateHere, s + end end - raise BetterHtml::HtmlError, s - end + def check_parser_errors + errors = @parser.errors + return if errors.empty? - def check_token(type, *args) - check_tag_name(type, *args) if type == :tag_name - check_attribute_name(type, *args) if type == :attribute_name - check_quoted_value(type, *args) if type == :attribute_quoted_value_start - check_unquoted_value(type, *args) if type == :attribute_unquoted_value - end + s = +"#{errors.size} error(s) found in HTML document.\n" + errors.each do |error| + s << "#{error.message}\n" + s << "On line #{error.line} column #{error.column}:\n" + line = extract_line(error.line) + s << "#{line}\n" + s << "#{" " * error.column}#{"^" * (line.size - error.column)}" + end - def check_tag_name(type, start, stop, line, column) - text = @parser.document[start...stop] - return if text.upcase == "!DOCTYPE" - return if @config.partial_tag_name_pattern === text + raise BetterHtml::HtmlError, s + end - s = "Invalid tag name #{text.inspect} does not match "\ - "regular expression #{@config.partial_tag_name_pattern.inspect}\n" - s << build_location(line, column, text.size) - raise BetterHtml::HtmlError, s - end + def check_token(type, *args) + check_tag_name(type, *args) if type == :tag_name + check_attribute_name(type, *args) if type == :attribute_name + check_quoted_value(type, *args) if type == :attribute_quoted_value_start + check_unquoted_value(type, *args) if type == :attribute_unquoted_value + end - def check_attribute_name(type, start, stop, line, column) - text = @parser.document[start...stop] - return if @config.partial_attribute_name_pattern === text + def check_tag_name(type, start, stop, line, column) + text = @parser.document[start...stop] + return if text.upcase == "!DOCTYPE" + return if @config.partial_tag_name_pattern.match?(text) - s = "Invalid attribute name #{text.inspect} does not match "\ - "regular expression #{@config.partial_attribute_name_pattern.inspect}\n" - s << build_location(line, column, text.size) - raise BetterHtml::HtmlError, s - end + s = +"Invalid tag name #{text.inspect} does not match "\ + "regular expression #{@config.partial_tag_name_pattern.inspect}\n" + s << build_location(line, column, text.size) + raise BetterHtml::HtmlError, s + end - def check_quoted_value(type, start, stop, line, column) - return if @config.allow_single_quoted_attributes - text = @parser.document[start...stop] - return if text == '"' + def check_attribute_name(type, start, stop, line, column) + text = @parser.document[start...stop] + return if @config.partial_attribute_name_pattern.match?(text) - s = "Single-quoted attributes are not allowed\n" - s << build_location(line, column, text.size) - raise BetterHtml::HtmlError, s - end + s = +"Invalid attribute name #{text.inspect} does not match "\ + "regular expression #{@config.partial_attribute_name_pattern.inspect}\n" + s << build_location(line, column, text.size) + raise BetterHtml::HtmlError, s + end - def check_unquoted_value(type, start, stop, line, column) - return if @config.allow_unquoted_attributes - s = "Unquoted attribute values are not allowed\n" - s << build_location(line, column, stop-start) - raise BetterHtml::HtmlError, s - end + def check_quoted_value(type, start, stop, line, column) + return if @config.allow_single_quoted_attributes - def build_location(line, column, length) - s = "On line #{line} column #{column}:\n" - s << "#{extract_line(line)}\n" - s << "#{' ' * column}#{'^' * length}" - end + text = @parser.document[start...stop] + return if text == '"' - def extract_line(line) - line = @parser.document.lines[line-1] - line.nil? ? "" : line.gsub(/\n$/, '') + s = +"Single-quoted attributes are not allowed\n" + s << build_location(line, column, text.size) + raise BetterHtml::HtmlError, s + end + + def check_unquoted_value(type, start, stop, line, column) + return if @config.allow_unquoted_attributes + + s = +"Unquoted attribute values are not allowed\n" + s << build_location(line, column, stop - start) + raise BetterHtml::HtmlError, s + end + + def build_location(line, column, length) + s = +"On line #{line} column #{column}:\n" + s << "#{extract_line(line)}\n" + s << "#{" " * column}#{"^" * length}" + end + + def extract_line(line) + line = @parser.document.lines[line - 1] + line.nil? ? "" : line.gsub(/\n$/, "") + end end end end diff --git a/lib/better_html/better_erb/validated_output_buffer.rb b/lib/better_html/better_erb/validated_output_buffer.rb index 134c2f7..3544b46 100644 --- a/lib/better_html/better_erb/validated_output_buffer.rb +++ b/lib/better_html/better_erb/validated_output_buffer.rb @@ -1,10 +1,8 @@ +# frozen_string_literal: true + module BetterHtml class BetterErb class ValidatedOutputBuffer - def self.wrap(output, context, code, auto_escape) - Context.new(output, context, code, auto_escape) - end - class Context def initialize(output, context, code, auto_escape) @output = output @@ -15,6 +13,7 @@ def initialize(output, context, code, auto_escape) def safe_quoted_value_append=(value) return if value.nil? + value = properly_escaped(value) if value.include?(@context[:quote_character]) @@ -22,7 +21,7 @@ def safe_quoted_value_append=(value) "into a quoted attribute value. The value cannot contain the character #{@context[:quote_character]}." end - @output.safe_append= value + @output.safe_append = value end def safe_unquoted_value_append=(value) @@ -40,6 +39,7 @@ def safe_space_after_attribute_append=(value) def safe_attribute_name_append=(value) return if value.nil? + value = value.to_s unless value =~ /\A[a-z0-9\-]*\z/ @@ -47,7 +47,7 @@ def safe_attribute_name_append=(value) "into a attribute name around '#{@context[:attribute_name]}<%=#{@code}%>'." end - @output.safe_append= value + @output.safe_append = value end def safe_after_attribute_name_append=(value) @@ -59,7 +59,7 @@ def safe_after_attribute_name_append=(value) "try <#{@context[:tag_name]} <%= html_attributes(attr: value) %>>." end - @output.safe_append= value.to_s + @output.safe_append = value.to_s end def safe_after_equal_append=(value) @@ -76,11 +76,12 @@ def safe_tag_append=(value) "try <#{@context[:tag_name]} <%= html_attributes(attr: value) %>>." end - @output.safe_append= value.to_s + @output.safe_append = value.to_s end def safe_tag_name_append=(value) return if value.nil? + value = value.to_s unless value =~ /\A[a-z0-9\:\-]*\z/ @@ -88,7 +89,7 @@ def safe_tag_name_append=(value) "into a tag name around: <#{@context[:tag_name]}<%=#{@code}%>>." end - @output.safe_append= value + @output.safe_append = value end def safe_rawtext_append=(value) @@ -96,23 +97,25 @@ def safe_rawtext_append=(value) value = properly_escaped(value) - if @context[:tag_name].downcase == 'script' && - (value =~ /#{@context[:rawtext_text]}<%=#{@code}%>." + "into a #{@context[:tag_name].downcase} tag around: " \ + "<#{@context[:tag_name]}>#{@context[:rawtext_text]}<%=#{@code}%>." end - @output.safe_append= value + @output.safe_append = value end def safe_comment_append=(value) return if value.nil? + value = properly_escaped(value) # in a we disallow --> @@ -121,12 +124,13 @@ def safe_comment_append=(value) "into a html comment around: ", - render("", locals: { value: "safe" }) - end - - test "interpolate in comment with end-of-comment" do - e = assert_raises(ActionView::Template::Error) do - render("", locals: { value: "-->".html_safe }) - end - assert_kind_of BetterHtml::UnsafeHtmlError, e.cause - assert_equal "Detected invalid characters as part of the interpolation "\ - "into a html comment around: ", - render("", locals: { value: '-->' }) - end - - test "interpolate in script tag" do - assert_equal "", - render("", locals: { value: "safe" }) - end - - test "interpolate in script tag with start of comment" do - skip "skip for now; causing problems" - e = assert_raises(ActionView::Template::Error) do - render("\n\n bla") - assert_equal :none, @parser.context - end + ["title", "textarea", "style", "xmp", "iframe", "noembed", "noframes"].each do |name| + define_method "test_#{name}_rawtext" do + parse("<#{name}>") + assert_equal :rawtext, @parser.context + assert_equal name, @parser.tag_name + assert_nil @parser.rawtext_text + + parse("some", "") + assert_equal :none, @parser.context + assert_equal "somedata data data") + assert_equal(:rawtext, @parser.context) + assert_equal("script", @parser.tag_name) + assert_equal("data data data", @parser.rawtext_text) + parse("") + assert_equal(:none, @parser.context) + end - def test_document_length - @parser = HtmlTokenizer::Parser.new - assert_equal 0, @parser.document_length - parse("abcdef") - assert_equal 6, @parser.document_length - parse("abcdef") - assert_equal 12, @parser.document_length - end + def test_consecutive_scripts + parse("\n\n bla") + assert_equal(:none, @parser.context) + end - def test_document_method - @parser = HtmlTokenizer::Parser.new - assert_nil @parser.document - parse("abcdef") - assert_equal "abcdef", @parser.document - parse("abcdef") - assert_equal "abcdefabcdef", @parser.document - end + def test_end_of_script_regression + html = "" + parse(html) + assert_equal(:none, @parser.context) + end - def test_yields_raw_tokens_when_block_given - tokens = [] - parse("") do |*token| - tokens << token + def test_document_length + @parser = HtmlTokenizer::Parser.new + assert_equal(0, @parser.document_length) + parse("abcdef") + assert_equal(6, @parser.document_length) + parse("abcdef") + assert_equal(12, @parser.document_length) end - assert_equal [[:tag_start, 0, 1, 1, 0], [:tag_name, 1, 4, 1, 1], [:tag_end, 4, 5, 1, 4]], tokens - end - def test_yields_line_and_column_numbers - tokens = [] - parse("<\n>") do |*token| - tokens << token + def test_document_method + @parser = HtmlTokenizer::Parser.new + assert_nil(@parser.document) + parse("abcdef") + assert_equal("abcdef", @parser.document) + parse("abcdef") + assert_equal("abcdefabcdef", @parser.document) end - assert_equal [[:tag_start, 0, 1, 1, 0], [:whitespace, 1, 2, 1, 1], [:tag_end, 2, 3, 2, 0]], tokens - end - def test_append_placeholder_adjusts_line_and_column_numbers_but_does_not_parse - @parser = HtmlTokenizer::Parser.new - tokens = [] - @parser.parse("foo\n") do |*token| - tokens << token + def test_yields_raw_tokens_when_block_given + tokens = [] + parse("") do |*token| + tokens << token + end + assert_equal([[:tag_start, 0, 1, 1, 0], [:tag_name, 1, 4, 1, 1], [:tag_end, 4, 5, 1, 4]], tokens) end - @parser.append_placeholder("<%= some ruby do\n foo\nend %>\n") do |*token| - tokens << token + + def test_yields_line_and_column_numbers + tokens = [] + parse("<\n>") do |*token| + tokens << token + end + assert_equal([[:tag_start, 0, 1, 1, 0], [:whitespace, 1, 2, 1, 1], [:tag_end, 2, 3, 2, 0]], tokens) end - @parser.parse("bar\n") do |*token| - tokens << token + + def test_append_placeholder_adjusts_line_and_column_numbers_but_does_not_parse + @parser = HtmlTokenizer::Parser.new + tokens = [] + @parser.parse("foo\n") do |*token| + tokens << token + end + @parser.append_placeholder("<%= some ruby do\n foo\nend %>\n") do |*token| + tokens << token + end + @parser.parse("bar\n") do |*token| + tokens << token + end + assert_equal([[:text, 0, 4, 1, 0], [:text, 34, 38, 5, 0]], tokens) end - assert_equal [[:text, 0, 4, 1, 0], [:text, 34, 38, 5, 0]], tokens - end - def test_solidus_or_tag_name_error - parse('<>') - assert_equal 1, @parser.errors_count - assert_equal "expected '/' or tag name", @parser.errors.first.to_s - assert_equal 1, @parser.errors.first.position - assert_equal 1, @parser.errors.first.line - assert_equal 1, @parser.errors.first.column - end + def test_solidus_or_tag_name_error + parse("<>") + assert_equal(1, @parser.errors_count) + assert_equal("expected '/' or tag name", @parser.errors.first.to_s) + assert_equal(1, @parser.errors.first.position) + assert_equal(1, @parser.errors.first.line) + assert_equal(1, @parser.errors.first.column) + end - def test_solidus_or_tag_name_error_2 - parse('< ') - assert_equal 1, @parser.errors_count - assert_equal "expected '/' or tag name", @parser.errors.first.to_s - assert_equal 1, @parser.errors.first.position - assert_equal 1, @parser.errors.first.line - assert_equal 1, @parser.errors.first.column - end + def test_solidus_or_tag_name_error_2 + parse("< ") + assert_equal(1, @parser.errors_count) + assert_equal("expected '/' or tag name", @parser.errors.first.to_s) + assert_equal(1, @parser.errors.first.position) + assert_equal(1, @parser.errors.first.line) + assert_equal(1, @parser.errors.first.column) + end - def test_tag_error - parse('', attribute name or value", @parser.errors.first.to_s - assert_equal 5, @parser.errors.first.position - assert_equal 1, @parser.errors.first.line - assert_equal 5, @parser.errors.first.column - end + def test_tag_error + parse("', attribute name or value", @parser.errors.first.to_s) + assert_equal(5, @parser.errors.first.position) + assert_equal(1, @parser.errors.first.line) + assert_equal(5, @parser.errors.first.column) + end - def test_tag_end_error - parse('' after '/'", @parser.errors.first.to_s - assert_equal 6, @parser.errors.first.position - assert_equal 1, @parser.errors.first.line - assert_equal 6, @parser.errors.first.column - end + def test_tag_end_error + parse("' after '/'", @parser.errors.first.to_s) + assert_equal(6, @parser.errors.first.position) + assert_equal(1, @parser.errors.first.line) + assert_equal(6, @parser.errors.first.column) + end - def test_tag_end_error_2 - parse('' after '/'", @parser.errors.first.to_s - assert_equal 6, @parser.errors.first.position - assert_equal 1, @parser.errors.first.line - assert_equal 6, @parser.errors.first.column - end + def test_tag_end_error_2 + parse("' after '/'", @parser.errors.first.to_s) + assert_equal(6, @parser.errors.first.position) + assert_equal(1, @parser.errors.first.line) + assert_equal(6, @parser.errors.first.column) + end - def test_attribute_name_error - parse('' or '=' after attribute name", @parser.errors[0].to_s - assert_equal 8, @parser.errors.first.position - assert_equal 1, @parser.errors[0].line - assert_equal 8, @parser.errors[0].column - assert_equal "expected whitespace, '>', attribute name or value", @parser.errors[1].to_s - assert_equal 8, @parser.errors.first.position - assert_equal 1, @parser.errors[1].line - assert_equal 8, @parser.errors[1].column - end + def test_attribute_name_error + parse("' or '=' after attribute name", @parser.errors[0].to_s) + assert_equal(8, @parser.errors.first.position) + assert_equal(1, @parser.errors[0].line) + assert_equal(8, @parser.errors[0].column) + assert_equal("expected whitespace, '>', attribute name or value", @parser.errors[1].to_s) + assert_equal(8, @parser.errors.first.position) + assert_equal(1, @parser.errors[1].line) + assert_equal(8, @parser.errors[1].column) + end - def test_attribute_whitespace_or_equal_error - parse('', \", ' or '=' after attribute name", @parser.errors[0].to_s - assert_equal 1, @parser.errors[0].line - assert_equal 9, @parser.errors[0].column - assert_equal "expected whitespace, '>', attribute name or value", @parser.errors[1].to_s - assert_equal 9, @parser.errors.first.position - assert_equal 1, @parser.errors[1].line - assert_equal 9, @parser.errors[1].column - end + def test_attribute_whitespace_or_equal_error + parse("', \", ' or '=' after attribute name", @parser.errors[0].to_s) + assert_equal(1, @parser.errors[0].line) + assert_equal(9, @parser.errors[0].column) + assert_equal("expected whitespace, '>', attribute name or value", @parser.errors[1].to_s) + assert_equal(9, @parser.errors.first.position) + assert_equal(1, @parser.errors[1].line) + assert_equal(9, @parser.errors[1].column) + end - def test_attribute_whitespace_or_equal_error_2 - parse('') - assert_equal 1, @parser.errors_count - assert_equal "expected attribute value after '='", @parser.errors.first.to_s - assert_equal 11, @parser.errors.first.position - assert_equal 1, @parser.errors.first.line - assert_equal 11, @parser.errors.first.column - end + def test_attribute_whitespace_or_equal_error_2 + parse("") + assert_equal(1, @parser.errors_count) + assert_equal("expected attribute value after '='", @parser.errors.first.to_s) + assert_equal(11, @parser.errors.first.position) + assert_equal(1, @parser.errors.first.line) + assert_equal(11, @parser.errors.first.column) + end - def test_attribute_after_quoted_value - parse('"] - tokens = [] - parse(*data) { |name, start, stop| tokens << [name, start, stop, data.join[start...stop]] } - assert_equal "div", @parser.tag_name - assert_equal "title", @parser.attribute_name - assert_equal "your store’s", @parser.attribute_value - assert_equal data.join, @parser.document - assert_equal data.join.size, @parser.document_length - assert_equal data.join.size, @parser.column_number - assert_equal [ - [:tag_start, 0, 1, "<"], - [:tag_name, 1, 4, "div"], - [:whitespace, 4, 5, " "], - [:attribute_name, 5, 10, "title"], - [:equal, 10, 11, "="], - [:attribute_quoted_value_start, 11, 12, "'"], - [:attribute_quoted_value, 12, 24, "your store’s"], - [:attribute_quoted_value_end, 24, 25, "'"], - [:tag_end, 25, 26, ">"], - ], tokens - end + def test_attribute_with_mutlibyte_characters + data = ["
"] + tokens = [] + parse(*data) { |name, start, stop| tokens << [name, start, stop, data.join[start...stop]] } + assert_equal("div", @parser.tag_name) + assert_equal("title", @parser.attribute_name) + assert_equal("your store’s", @parser.attribute_value) + assert_equal(data.join, @parser.document) + assert_equal(data.join.size, @parser.document_length) + assert_equal(data.join.size, @parser.column_number) + assert_equal([ + [:tag_start, 0, 1, "<"], + [:tag_name, 1, 4, "div"], + [:whitespace, 4, 5, " "], + [:attribute_name, 5, 10, "title"], + [:equal, 10, 11, "="], + [:attribute_quoted_value_start, 11, 12, "'"], + [:attribute_quoted_value, 12, 24, "your store’s"], + [:attribute_quoted_value_end, 24, 25, "'"], + [:tag_end, 25, 26, ">"], + ], tokens) + end - def test_valid_syntaxes - parse( - '
', - '
', - '
', - '
', - '
', - '
', - '
', - '
', - '
', - '
', - '
', - '
', - '
', - '
', - "
", - '
', - '
', - '
', - '
', - '
', - '
', - '
', - '
', - '', - ) - assert_equal 0, @parser.errors_count, "Expected no errors: #{@parser.errors}" - end + def test_valid_syntaxes + parse( + "
", + "
", + "
", + "
", + "
", + "
", + '
', + '
', + '
', + '
', + '
', + '
', + '
', + '
', + "
", + '
', + '
', + '
', + '
', + '
', + "
", + "
", + "
", + '', + ) + assert_equal(0, @parser.errors_count, "Expected no errors: #{@parser.errors}") + end - def test_doctype_without_space - parse('"], - ], tokenize(">") - assert_equal [ - [:tag_start, "<"], [:tag_name, "foo"], [:tag_end, ">"], [:text, ">"], - ], tokenize(">") - end +module HtmlTokenizer + class TokenizerTest < Minitest::Test + def test_closing_tag_without_start_is_text + assert_equal([ + [:text, ">"], + ], tokenize(">")) + assert_equal([ + [:tag_start, "<"], [:tag_name, "foo"], [:tag_end, ">"], [:text, ">"], + ], tokenize(">")) + end - def test_tokenize_text - result = tokenize("\n hello world\n ") - assert_equal [[:text, "\n hello world\n "]], result - end + def test_tokenize_text + result = tokenize("\n hello world\n ") + assert_equal([[:text, "\n hello world\n "]], result) + end - def test_namespace_tag_name_multipart - assert_equal [ - [:tag_start, "<"], [:tag_name, "foo:"], [:tag_name, "bar"], - ], tokenize(""] - ], tokenize("") - end + def test_tokenize_doctype + assert_equal([ + [:tag_start, "<"], [:tag_name, "!DOCTYPE"], [:whitespace, " "], + [:attribute_name, "html"], [:tag_end, ">"], + ], tokenize("")) + end - def test_tokenize_multiple_elements - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:tag_end, ">"], - [:text, " bla "], - [:tag_start, "<"], [:tag_name, "strong"], [:tag_end, ">"] - ], tokenize("
bla ") - end + def test_tokenize_multiple_elements + assert_equal([ + [:tag_start, "<"], [:tag_name, "div"], [:tag_end, ">"], + [:text, " bla "], + [:tag_start, "<"], [:tag_name, "strong"], [:tag_end, ">"], + ], tokenize("
bla ")) + end - def test_tokenize_complex_doctype - text = '' - assert_equal [ - [:tag_start, "<"], [:tag_name, "!DOCTYPE"], [:whitespace, " "], - [:attribute_name, "html"], [:whitespace, " "], - [:attribute_name, "PUBLIC"], [:whitespace, " "], - [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "-//W3C//DTD XHTML 1.0 Transitional//EN"], [:attribute_quoted_value_end, "\""], - [:whitespace, " "], - [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"], [:attribute_quoted_value_end, "\""], - [:tag_end, ">"] - ], tokenize(text) - end + def test_tokenize_complex_doctype + text = '' + assert_equal([ + [:tag_start, "<"], + [:tag_name, "!DOCTYPE"], + [:whitespace, " "], + [:attribute_name, "html"], + [:whitespace, " "], + [:attribute_name, "PUBLIC"], + [:whitespace, " "], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "-//W3C//DTD XHTML 1.0 Transitional//EN"], + [:attribute_quoted_value_end, "\""], + [:whitespace, " "], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"], + [:attribute_quoted_value_end, "\""], + [:tag_end, ">"], + ], tokenize(text)) + end - def test_tokenize_html_comment - result = tokenize("") - assert_equal [[:comment_start, ""]], result - end + def test_tokenize_html_comment + result = tokenize("") + assert_equal([[:comment_start, ""]], result) + end - def test_tokenize_comment_with_newlines - result = tokenize <<-EOF + def test_tokenize_comment_with_newlines + result = tokenize(<<-EOF) - EOF + EOF - assert_equal [ - [:text, " "], [:comment_start, ""], [:text, "\n"] - ], result - end + assert_equal([ + [:text, " "], [:comment_start, ""], [:text, "\n"], + ], result) + end - def test_tokenize_cdata_section - result = tokenize(" foo ]]>") - assert_equal [[:cdata_start, " foo "], [:cdata_end, "]]>"]], result - end + def test_tokenize_cdata_section + result = tokenize(" foo ]]>") + assert_equal([[:cdata_start, " foo "], [:cdata_end, "]]>"]], result) + end - def test_tokenizer_cdata_regression - result = tokenize("") - assert_equal [[:cdata_start, ""]], result - end + def test_tokenizer_cdata_regression + result = tokenize("") + assert_equal([[:cdata_start, ""],], result) + end - def test_tokenizer_comment_regression - result = tokenize("") - assert_equal [[:comment_start, ""]], result - end + def test_tokenizer_comment_regression + result = tokenize("") + assert_equal([[:comment_start, ""],], result) + end - def test_tokenizer_parse_tag_after_comment_regression - result = tokenize("
  • ") - assert_equal [[:comment_start, ""], - [:text, " "], [:tag_start, "<"], [:tag_name, "li"], [:tag_end, ">"]], result - end + def test_tokenizer_parse_tag_after_comment_regression + result = tokenize("
  • ") + assert_equal([[:comment_start, ""], + [:text, " "], [:tag_start, "<"], [:tag_name, "li"], [:tag_end, ">"],], result) + end - def test_tokenize_basic_tag - result = tokenize("
    ") - assert_equal [[:tag_start, "<"], [:tag_name, "div"], [:tag_end, ">"]], result - end + def test_tokenize_basic_tag + result = tokenize("
    ") + assert_equal([[:tag_start, "<"], [:tag_name, "div"], [:tag_end, ">"]], result) + end - def test_tokenize_namespaced_tag - result = tokenize("") - assert_equal [[:tag_start, "<"], [:tag_name, "ns:foo"], [:tag_end, ">"]], result - end + def test_tokenize_namespaced_tag + result = tokenize("") + assert_equal([[:tag_start, "<"], [:tag_name, "ns:foo"], [:tag_end, ">"]], result) + end - def test_tokenize_tag_with_lt - result = tokenize("") - assert_equal [[:tag_start, "<"], [:tag_name, "a"]], result - end + def test_tokenize_tag_with_lt + result = tokenize("") + assert_equal([[:tag_start, "<"], [:tag_name, "a"]], result) + end - def test_tokenize_tag_multipart_name - result = tokenize("") - assert_equal [[:tag_start, "<"], [:tag_name, "d"], [:tag_name, "iv"], [:tag_end, ">"]], result - end + def test_tokenize_tag_multipart_name + result = tokenize("") + assert_equal([[:tag_start, "<"], [:tag_name, "d"], [:tag_name, "iv"], [:tag_end, ">"]], result) + end - def test_tokenize_tag_name_ending_with_slash - result = tokenize("
    ") - assert_equal [[:tag_start, "<"], [:tag_name, "div"], [:solidus, "/"], [:attribute_name, "1"], [:tag_end, ">"]], result - end + def test_tokenize_tag_name_ending_with_slash + result = tokenize("
    ") + assert_equal([[:tag_start, "<"], [:tag_name, "div"], [:solidus, "/"], [:attribute_name, "1"], [:tag_end, ">"]], + result) + end - def test_tokenize_empty_tag - result = tokenize("<>") - assert_equal [[:tag_start, "<"], [:tag_end, ">"]], result - end + def test_tokenize_empty_tag + result = tokenize("<>") + assert_equal([[:tag_start, "<"], [:tag_end, ">"]], result) + end - def test_tokenize_tag_with_solidus - result = tokenize("") - assert_equal [[:tag_start, "<"], [:solidus, "/"], [:tag_end, ">"]], result - end + def test_tokenize_tag_with_solidus + result = tokenize("") + assert_equal([[:tag_start, "<"], [:solidus, "/"], [:tag_end, ">"]], result) + end - def test_tokenize_end_tag - result = tokenize("
    ") - assert_equal [[:tag_start, "<"], [:solidus, "/"], [:tag_name, "div"], [:tag_end, ">"]], result - end + def test_tokenize_end_tag + result = tokenize("
    ") + assert_equal([[:tag_start, "<"], [:solidus, "/"], [:tag_name, "div"], [:tag_end, ">"]], result) + end - def test_tokenize_tag_attribute_with_double_quote - result = tokenize('
    ') - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "bar"], [:attribute_quoted_value_end, "\""], - [:tag_end, ">"] - ], result - end + def test_tokenize_tag_attribute_with_double_quote + result = tokenize('
    ') + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "foo"], + [:equal, "="], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "bar"], + [:attribute_quoted_value_end, "\""], + [:tag_end, ">"], + ], result) + end - def test_tokenize_unquoted_attributes_separated_with_solidus - result = tokenize('
    ') - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_unquoted_value, "1/bar=2"], - [:tag_end, ">"] - ], result - end + def test_tokenize_unquoted_attributes_separated_with_solidus + result = tokenize("
    ") + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "foo"], + [:equal, "="], + [:attribute_unquoted_value, "1/bar=2"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_quoted_attributes_separated_with_solidus - result = tokenize('
    ') - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "1"], [:attribute_quoted_value_end, "\""], - [:solidus, "/"], - [:attribute_name, "bar"], [:equal, "="], [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "2"], [:attribute_quoted_value_end, "\""], - [:tag_end, ">"] - ], result - end + def test_tokenize_quoted_attributes_separated_with_solidus + result = tokenize('
    ') + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "foo"], + [:equal, "="], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "1"], + [:attribute_quoted_value_end, "\""], + [:solidus, "/"], + [:attribute_name, "bar"], + [:equal, "="], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "2"], + [:attribute_quoted_value_end, "\""], + [:tag_end, ">"], + ], result) + end - def test_tokenize_tag_attribute_without_space - result = tokenize('
    ') - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "bar"], [:attribute_quoted_value_end, "\""], - [:attribute_name, "baz"], - [:tag_end, ">"] - ], result - end + def test_tokenize_tag_attribute_without_space + result = tokenize('
    ') + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "foo"], + [:equal, "="], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "bar"], + [:attribute_quoted_value_end, "\""], + [:attribute_name, "baz"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_multipart_unquoted_attribute - result = tokenize('
    ') - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_unquoted_value, "bar"], - [:attribute_unquoted_value, "baz"], [:tag_end, ">"] - ], result - end + def test_tokenize_multipart_unquoted_attribute + result = tokenize("
    ") + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "foo"], + [:equal, "="], + [:attribute_unquoted_value, "bar"], + [:attribute_unquoted_value, "baz"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_quoted_attribute_separately - result = tokenize('
    ') - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_quoted_value_start, "'"], [:attribute_quoted_value, "bar"], [:attribute_quoted_value_end, "'"], - [:tag_end, ">"] - ], result - end + def test_tokenize_quoted_attribute_separately + result = tokenize("
    ") + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "foo"], + [:equal, "="], + [:attribute_quoted_value_start, "'"], + [:attribute_quoted_value, "bar"], + [:attribute_quoted_value_end, "'"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_quoted_attribute_in_multiple_parts - result = tokenize('
    ') - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_quoted_value_start, "'"], [:attribute_quoted_value, "bar"], [:attribute_quoted_value, "baz"], [:attribute_quoted_value_end, "'"], - [:tag_end, ">"] - ], result - end + def test_tokenize_quoted_attribute_in_multiple_parts + result = tokenize("
    ") + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "foo"], + [:equal, "="], + [:attribute_quoted_value_start, "'"], + [:attribute_quoted_value, "bar"], + [:attribute_quoted_value, "baz"], + [:attribute_quoted_value_end, "'"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_tag_attribute_with_single_quote - result = tokenize("
    ") - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_quoted_value_start, "'"], [:attribute_quoted_value, "bar"], [:attribute_quoted_value_end, "'"], - [:tag_end, ">"] - ], result - end + def test_tokenize_tag_attribute_with_single_quote + result = tokenize("
    ") + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "foo"], + [:equal, "="], + [:attribute_quoted_value_start, "'"], + [:attribute_quoted_value, "bar"], + [:attribute_quoted_value_end, "'"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_tag_attribute_with_no_quotes - result = tokenize("
    ") - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_unquoted_value, "bla"], [:whitespace, " "], - [:attribute_name, "bar"], [:equal, "="], [:attribute_unquoted_value, "blo"], - [:tag_end, ">"] - ], result - end + def test_tokenize_tag_attribute_with_no_quotes + result = tokenize("
    ") + assert_equal([ + [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], + [:attribute_name, "foo"], [:equal, "="], [:attribute_unquoted_value, "bla"], [:whitespace, " "], + [:attribute_name, "bar"], [:equal, "="], [:attribute_unquoted_value, "blo"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_double_equals - result = tokenize("
    ") - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_unquoted_value, "blabar=blo"], - [:tag_end, ">"] - ], result - end + def test_tokenize_double_equals + result = tokenize("
    ") + assert_equal([ + [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], + [:attribute_name, "foo"], [:equal, "="], [:attribute_unquoted_value, "blabar=blo"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_closing_tag - result = tokenize('
    ') - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "foo"], [:equal, "="], [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "bar"], [:attribute_quoted_value_end, "\""], [:whitespace, " "], - [:solidus, "/"], [:tag_end, ">"] - ], result - end + def test_tokenize_closing_tag + result = tokenize('
    ') + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "foo"], + [:equal, "="], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "bar"], + [:attribute_quoted_value_end, "\""], + [:whitespace, " "], + [:solidus, "/"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_script_tag - result = tokenize('') - assert_equal [ - [:tag_start, "<"], [:tag_name, "script"], [:tag_end, ">"], - [:text, "foo "], [:text, " bar"], - [:tag_start, "<"], [:solidus, "/"], [:tag_name, "script"], [:tag_end, ">"], - ], result - end + def test_tokenize_script_tag + result = tokenize("") + assert_equal([ + [:tag_start, "<"], [:tag_name, "script"], [:tag_end, ">"], + [:text, "foo "], [:text, " bar"], + [:tag_start, "<"], [:solidus, "/"], [:tag_name, "script"], [:tag_end, ">"], + ], result) + end - def test_tokenize_textarea_tag - result = tokenize('') - assert_equal [ - [:tag_start, "<"], [:tag_name, "textarea"], [:tag_end, ">"], - [:text, "hello"], - [:tag_start, "<"], [:solidus, "/"], [:tag_name, "textarea"], [:tag_end, ">"], - ], result - end + def test_tokenize_textarea_tag + result = tokenize("") + assert_equal([ + [:tag_start, "<"], [:tag_name, "textarea"], [:tag_end, ">"], + [:text, "hello"], + [:tag_start, "<"], [:solidus, "/"], [:tag_name, "textarea"], [:tag_end, ">"], + ], result) + end - def test_tokenize_style_tag - result = tokenize('') - assert_equal [ - [:tag_start, "<"], [:tag_name, "style"], [:tag_end, ">"], - [:text, ""], - [:tag_start, "<"], [:solidus, "/"], [:tag_name, "style"], [:tag_end, ">"], - ], result - end + def test_tokenize_style_tag + result = tokenize("") + assert_equal([ + [:tag_start, "<"], [:tag_name, "style"], [:tag_end, ">"], + [:text, ""], + [:tag_start, "<"], [:solidus, "/"], [:tag_name, "style"], [:tag_end, ">"], + ], result) + end - def test_tokenize_script_containing_html - result = tokenize('') - assert_equal [ - [:tag_start, "<"], [:tag_name, "script"], [:whitespace, " "], - [:attribute_name, "type"], [:equal, "="], [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "text/html"], [:attribute_quoted_value_end, "\""], - [:tag_end, ">"], - [:text, "foo "], [:text, " bar"], - [:tag_start, "<"], [:solidus, "/"], [:tag_name, "script"], [:tag_end, ">"], - ], result - end + def test_tokenize_script_containing_html + result = tokenize('') + assert_equal([ + [:tag_start, "<"], + [:tag_name, "script"], + [:whitespace, " "], + [:attribute_name, "type"], + [:equal, "="], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "text/html"], + [:attribute_quoted_value_end, "\""], + [:tag_end, ">"], + [:text, "foo "], + [:text, " bar"], + [:tag_start, "<"], + [:solidus, "/"], + [:tag_name, "script"], + [:tag_end, ">"], + ], result) + end - def test_end_of_tag_on_newline - data = ["\ + def test_end_of_tag_on_newline + data = ["\
    "] - result = tokenize(*data) - assert_equal [ - [:text, " "], - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], [:attribute_name, "define"], [:equal, "="], [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "{credential_96_credential1: new Shopify.ProviderCredentials()}"], [:attribute_quoted_value_end, "\""], - [:whitespace, "\n "], [:tag_end, ">"] - ], result - end + ", "", ">",] + result = tokenize(*data) + assert_equal([ + [:text, " "], + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "define"], + [:equal, "="], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "{credential_96_credential1: new Shopify.ProviderCredentials()}"], + [:attribute_quoted_value_end, "\""], + [:whitespace, "\n "], + [:tag_end, ">"], + ], result) + end - def test_tokenize_multi_part_attribute_name - result = tokenize('
    ') - assert_equal [ - [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], - [:attribute_name, "data-"], [:attribute_name, "shipping"], [:attribute_name, "-type"], - [:tag_end, ">"], - ], result - end + def test_tokenize_multi_part_attribute_name + result = tokenize("
    ") + assert_equal([ + [:tag_start, "<"], [:tag_name, "div"], [:whitespace, " "], + [:attribute_name, "data-"], [:attribute_name, "shipping"], [:attribute_name, "-type"], + [:tag_end, ">"], + ], result) + end - def test_tokenize_attribute_name_with_space_before_equal - result = tokenize('GST/HST') - assert_equal [ - [:tag_start, "<"], [:tag_name, "a"], [:whitespace, " "], - [:attribute_name, "href"], [:whitespace, " "], [:equal, "="], - [:attribute_quoted_value_start, "\""], [:attribute_quoted_value, "http://www.cra-arc.gc.ca/tx/bsnss/tpcs/gst-tps/menu-eng.html"], [:attribute_quoted_value_end, "\""], - [:tag_end, ">"], [:text, "GST/HST"], - [:tag_start, "<"], [:solidus, "/"], [:tag_name, "a"], [:tag_end, ">"] - ], result - end + def test_tokenize_attribute_name_with_space_before_equal + result = tokenize('GST/HST') + assert_equal([ + [:tag_start, "<"], + [:tag_name, "a"], + [:whitespace, " "], + [:attribute_name, "href"], + [:whitespace, " "], + [:equal, "="], + [:attribute_quoted_value_start, "\""], + [:attribute_quoted_value, "http://www.cra-arc.gc.ca/tx/bsnss/tpcs/gst-tps/menu-eng.html"], + [:attribute_quoted_value_end, "\""], + [:tag_end, ">"], + [:text, "GST/HST"], + [:tag_start, "<"], + [:solidus, "/"], + [:tag_name, "a"], + [:tag_end, ">"], + ], result) + end - def test_raise_in_block - @tokenizer = HtmlTokenizer::Tokenizer.new - 10.times do - e = assert_raises(RuntimeError) do - @tokenizer.tokenize("<>") do |part| - raise RuntimeError, "something went wrong" + def test_raise_in_block + @tokenizer = HtmlTokenizer::Tokenizer.new + 10.times do + e = assert_raises(RuntimeError) do + @tokenizer.tokenize("<>") do |_part| + raise "something went wrong" + end end + assert_equal("something went wrong", e.message) end - assert_equal "something went wrong", e.message end - end - def test_tokenize_end_of_script_regression - result = tokenize("") - assert_equal [ - [:tag_start, "<"], [:tag_name, "script"], [:tag_end, ">"], - [:text, "<"], - [:tag_start, "<"], [:solidus, "/"], [:tag_name, "script"], [:tag_end, ">"] - ], result - end + def test_tokenize_end_of_script_regression + result = tokenize("") + assert_equal([ + [:tag_start, "<"], [:tag_name, "script"], [:tag_end, ">"], + [:text, "<"], + [:tag_start, "<"], [:solidus, "/"], [:tag_name, "script"], [:tag_end, ">"], + ], result) + end - def test_html_with_mutlibyte_characters - data = "
    foo
    " - result = tokenize(data) - assert_equal [ - [:tag_start, "<"], - [:tag_name, "div"], - [:whitespace, " "], - [:attribute_name, "title"], - [:equal, "="], - [:attribute_quoted_value_start, "'"], - [:attribute_quoted_value, "your store’s"], - [:attribute_quoted_value_end, "'"], - [:tag_end, ">"], - [:text, "foo"], - [:tag_start, "<"], - [:solidus, "/"], - [:tag_name, "div"], - [:tag_end, ">"], - ], result - end + def test_html_with_mutlibyte_characters + data = "
    foo
    " + result = tokenize(data) + assert_equal([ + [:tag_start, "<"], + [:tag_name, "div"], + [:whitespace, " "], + [:attribute_name, "title"], + [:equal, "="], + [:attribute_quoted_value_start, "'"], + [:attribute_quoted_value, "your store’s"], + [:attribute_quoted_value_end, "'"], + [:tag_end, ">"], + [:text, "foo"], + [:tag_start, "<"], + [:solidus, "/"], + [:tag_name, "div"], + [:tag_end, ">"], + ], result) + end - private + private - def tokenize(*parts) - tokens = [] - @tokenizer = HtmlTokenizer::Tokenizer.new - parts.each do |part| - @tokenizer.tokenize(part) { |name, start, stop| tokens << [name, part[start...stop]] } + def tokenize(*parts) + tokens = [] + @tokenizer = HtmlTokenizer::Tokenizer.new + parts.each do |part| + @tokenizer.tokenize(part) { |name, start, stop| tokens << [name, part[start...stop]] } + end + tokens end - tokens end end diff --git a/test/test_helper.rb b/test/test_helper.rb index dad6b71..cdccdd9 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require "active_support" require "minitest/autorun" -require 'better_html' +require "better_html" # Filter out Minitest backtrace while allowing backtrace from other libraries # to be shown. @@ -13,17 +15,19 @@ if ActiveSupport::TestCase.respond_to?(:fixture_path=) ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path - ActiveSupport::TestCase.fixtures :all + ActiveSupport::TestCase.fixtures(:all) end -require 'mocha/minitest' +require "mocha/minitest" -class ActiveSupport::TestCase - private +module ActiveSupport + class TestCase + private - def buffer(string) - buffer = ::Parser::Source::Buffer.new('(test)') - buffer.source = string - buffer + def buffer(string) + buffer = ::Parser::Source::Buffer.new("(test)") + buffer.source = string + buffer + end end end