better_html 1.0.16 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -0
  3. data/Rakefile +19 -14
  4. data/ext/better_html_ext/better_html.h +1 -0
  5. data/ext/better_html_ext/extconf.rb +16 -0
  6. data/ext/better_html_ext/html_tokenizer.c +12 -0
  7. data/ext/better_html_ext/html_tokenizer.h +7 -0
  8. data/ext/better_html_ext/parser.c +793 -0
  9. data/ext/better_html_ext/parser.h +93 -0
  10. data/ext/better_html_ext/tokenizer.c +717 -0
  11. data/ext/better_html_ext/tokenizer.h +80 -0
  12. data/lib/better_html/ast/iterator.rb +14 -9
  13. data/lib/better_html/ast/node.rb +4 -2
  14. data/lib/better_html/better_erb/erubi_implementation.rb +43 -39
  15. data/lib/better_html/better_erb/runtime_checks.rb +140 -133
  16. data/lib/better_html/better_erb/validated_output_buffer.rb +30 -22
  17. data/lib/better_html/better_erb.rb +58 -54
  18. data/lib/better_html/config.rb +7 -4
  19. data/lib/better_html/errors.rb +4 -2
  20. data/lib/better_html/helpers.rb +7 -3
  21. data/lib/better_html/html_attributes.rb +6 -2
  22. data/lib/better_html/parser.rb +21 -14
  23. data/lib/better_html/railtie.rb +8 -4
  24. data/lib/better_html/test_helper/ruby_node.rb +15 -10
  25. data/lib/better_html/test_helper/safe_erb/allowed_script_type.rb +8 -4
  26. data/lib/better_html/test_helper/safe_erb/base.rb +12 -9
  27. data/lib/better_html/test_helper/safe_erb/no_javascript_tag_helper.rb +7 -3
  28. data/lib/better_html/test_helper/safe_erb/no_statements.rb +7 -3
  29. data/lib/better_html/test_helper/safe_erb/script_interpolation.rb +9 -4
  30. data/lib/better_html/test_helper/safe_erb/tag_interpolation.rb +23 -20
  31. data/lib/better_html/test_helper/safe_erb_tester.rb +33 -31
  32. data/lib/better_html/test_helper/safe_lodash_tester.rb +36 -35
  33. data/lib/better_html/test_helper/safety_error.rb +2 -0
  34. data/lib/better_html/tokenizer/base_erb.rb +14 -10
  35. data/lib/better_html/tokenizer/html_erb.rb +3 -2
  36. data/lib/better_html/tokenizer/html_lodash.rb +22 -14
  37. data/lib/better_html/tokenizer/javascript_erb.rb +3 -1
  38. data/lib/better_html/tokenizer/location.rb +17 -6
  39. data/lib/better_html/tokenizer/token.rb +2 -0
  40. data/lib/better_html/tokenizer/token_array.rb +8 -8
  41. data/lib/better_html/tree/attribute.rb +10 -6
  42. data/lib/better_html/tree/attributes_list.rb +9 -5
  43. data/lib/better_html/tree/tag.rb +10 -6
  44. data/lib/better_html/version.rb +3 -1
  45. data/lib/better_html.rb +19 -17
  46. data/lib/tasks/better_html_tasks.rake +1 -0
  47. metadata +39 -147
  48. data/lib/better_html/better_erb/erubis_implementation.rb +0 -44
  49. data/test/better_html/better_erb/implementation_test.rb +0 -406
  50. data/test/better_html/errors_test.rb +0 -13
  51. data/test/better_html/helpers_test.rb +0 -49
  52. data/test/better_html/parser_test.rb +0 -314
  53. data/test/better_html/test_helper/ruby_node_test.rb +0 -288
  54. data/test/better_html/test_helper/safe_erb/allowed_script_type_test.rb +0 -46
  55. data/test/better_html/test_helper/safe_erb/no_javascript_tag_helper_test.rb +0 -37
  56. data/test/better_html/test_helper/safe_erb/no_statements_test.rb +0 -129
  57. data/test/better_html/test_helper/safe_erb/script_interpolation_test.rb +0 -149
  58. data/test/better_html/test_helper/safe_erb/tag_interpolation_test.rb +0 -303
  59. data/test/better_html/test_helper/safe_lodash_tester_test.rb +0 -90
  60. data/test/better_html/tokenizer/html_erb_test.rb +0 -180
  61. data/test/better_html/tokenizer/html_lodash_test.rb +0 -98
  62. data/test/better_html/tokenizer/location_test.rb +0 -75
  63. data/test/better_html/tokenizer/token_array_test.rb +0 -146
  64. data/test/better_html/tokenizer/token_test.rb +0 -15
  65. data/test/dummy/README.rdoc +0 -28
  66. data/test/dummy/Rakefile +0 -6
  67. data/test/dummy/app/assets/javascripts/application.js +0 -13
  68. data/test/dummy/app/assets/stylesheets/application.css +0 -15
  69. data/test/dummy/app/controllers/application_controller.rb +0 -5
  70. data/test/dummy/app/helpers/application_helper.rb +0 -2
  71. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  72. data/test/dummy/bin/bundle +0 -3
  73. data/test/dummy/bin/rails +0 -4
  74. data/test/dummy/bin/rake +0 -4
  75. data/test/dummy/bin/setup +0 -29
  76. data/test/dummy/config/application.rb +0 -26
  77. data/test/dummy/config/boot.rb +0 -5
  78. data/test/dummy/config/database.yml +0 -25
  79. data/test/dummy/config/environment.rb +0 -5
  80. data/test/dummy/config/environments/development.rb +0 -41
  81. data/test/dummy/config/environments/production.rb +0 -79
  82. data/test/dummy/config/environments/test.rb +0 -42
  83. data/test/dummy/config/initializers/assets.rb +0 -11
  84. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  85. data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
  86. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  87. data/test/dummy/config/initializers/inflections.rb +0 -16
  88. data/test/dummy/config/initializers/mime_types.rb +0 -4
  89. data/test/dummy/config/initializers/session_store.rb +0 -3
  90. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  91. data/test/dummy/config/locales/en.yml +0 -23
  92. data/test/dummy/config/routes.rb +0 -56
  93. data/test/dummy/config/secrets.yml +0 -22
  94. data/test/dummy/config.ru +0 -4
  95. data/test/dummy/public/404.html +0 -67
  96. data/test/dummy/public/422.html +0 -67
  97. data/test/dummy/public/500.html +0 -66
  98. data/test/dummy/public/favicon.ico +0 -0
  99. data/test/test_helper.rb +0 -29
@@ -1,5 +1,7 @@
1
- require_relative 'base'
2
- require 'better_html/test_helper/ruby_node'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require "better_html/test_helper/ruby_node"
3
5
 
4
6
  module BetterHtml
5
7
  module TestHelper
@@ -7,7 +9,7 @@ module BetterHtml
7
9
  class ScriptInterpolation < Base
8
10
  def validate
9
11
  script_tags.each do |tag, content_node|
10
- if content_node.present? && (tag.attributes['type']&.value || "text/javascript") == "text/javascript"
12
+ if content_node.present? && (tag.attributes["type"]&.value || "text/javascript") == "text/javascript"
11
13
  validate_script(content_node)
12
14
  end
13
15
  end
@@ -24,8 +26,10 @@ module BetterHtml
24
26
  def validate_script(node)
25
27
  erb_nodes(node).each do |erb_node, indicator_node, code_node|
26
28
  next unless indicator_node.present?
29
+
27
30
  indicator = indicator_node.loc.source
28
- next if indicator == '#' || indicator == '%'
31
+ next if indicator == "#" || indicator == "%"
32
+
29
33
  source = code_node.loc.source
30
34
 
31
35
  ruby_node = begin
@@ -34,6 +38,7 @@ module BetterHtml
34
38
  nil
35
39
  end
36
40
  next unless ruby_node
41
+
37
42
  validate_script_interpolation(erb_node, ruby_node)
38
43
  end
39
44
  end
@@ -1,15 +1,14 @@
1
- require_relative 'base'
2
- require 'better_html/test_helper/ruby_node'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require "better_html/test_helper/ruby_node"
3
5
 
4
6
  module BetterHtml
5
7
  module TestHelper
6
8
  module SafeErb
7
9
  class TagInterpolation < Base
8
-
9
- NO_HTML_TAGS = %w(
10
- title textarea script
11
- style xmp iframe noembed noframes listing plaintext
12
- )
10
+ NO_HTML_TAGS = ["title", "textarea", "script", "style", "xmp", "iframe", "noembed", "noframes", "listing",
11
+ "plaintext",]
13
12
 
14
13
  def validate
15
14
  @parser.nodes_with_type(:tag).each do |tag_node|
@@ -43,7 +42,7 @@ module BetterHtml
43
42
  indicator = indicator_node.loc.source
44
43
  source = code_node.loc.source
45
44
 
46
- if indicator == '='
45
+ if indicator == "="
47
46
  ruby_node = begin
48
47
  RubyNode.parse(source)
49
48
  rescue ::Parser::SyntaxError
@@ -55,7 +54,7 @@ module BetterHtml
55
54
  handle_missing_safe_wrapper(code_node, ruby_node, attribute.name)
56
55
  end
57
56
  end
58
- elsif indicator == '=='
57
+ elsif indicator == "=="
59
58
  add_error(
60
59
  "erb interpolation with '<%==' inside html attribute is never safe",
61
60
  location: erb_node.loc
@@ -65,9 +64,10 @@ module BetterHtml
65
64
  end
66
65
 
67
66
  def validate_text_node(text_node)
68
- erb_nodes(text_node).each do |erb_node, indicator_node, code_node|
67
+ erb_nodes(text_node).each do |_erb_node, indicator_node, code_node|
69
68
  indicator = indicator_node&.loc&.source
70
- next if indicator == '#' || indicator == '%'
69
+ next if indicator == "#" || indicator == "%"
70
+
71
71
  source = code_node.loc.source
72
72
 
73
73
  ruby_node = begin
@@ -76,6 +76,7 @@ module BetterHtml
76
76
  nil
77
77
  end
78
78
  next unless ruby_node
79
+
79
80
  no_unsafe_calls(code_node, ruby_node)
80
81
  validate_ruby_helper(code_node, ruby_node)
81
82
  end
@@ -93,12 +94,13 @@ module BetterHtml
93
94
 
94
95
  def validate_ruby_helper_hash_entry(parent_node, ruby_node, key_prefix, key_node, value_node)
95
96
  return unless [:sym, :str].include?(key_node.type)
96
- key = [key_prefix, key_node.children.first.to_s].compact.join('-').dasherize
97
+
98
+ key = [key_prefix, key_node.children.first.to_s].compact.join("-").dasherize
97
99
  case value_node.type
98
100
  when :dstr
99
101
  validate_ruby_helper_hash_value(parent_node, ruby_node, key, value_node)
100
102
  when :hash
101
- if key == 'data'
103
+ if key == "data"
102
104
  value_node.child_nodes.select(&:pair?).each do |pair_node|
103
105
  validate_ruby_helper_hash_entry(parent_node, ruby_node, key, *pair_node.children)
104
106
  end
@@ -114,6 +116,7 @@ module BetterHtml
114
116
 
115
117
  def handle_missing_safe_wrapper(parent_node, ruby_node, attr_name)
116
118
  return unless @config.javascript_attribute_name?(attr_name)
119
+
117
120
  method_calls = ruby_node.return_values.select(&:method_call?)
118
121
  unsafe_calls = method_calls.select { |node| !@config.javascript_safe_method?(node.method_name) }
119
122
  if method_calls.empty?
@@ -140,13 +143,13 @@ module BetterHtml
140
143
  ruby_node.return_values.each do |call_node|
141
144
  next if call_node.static_return_value?
142
145
 
143
- if @config.javascript_attribute_name?(attr_name) &&
144
- !@config.javascript_safe_method?(call_node.method_name)
145
- add_error(
146
- "erb interpolation in javascript attribute must be wrapped in safe helper such as '(...).to_json'",
147
- location: nested_location(parent_node, ruby_node)
148
- )
149
- end
146
+ next unless @config.javascript_attribute_name?(attr_name) &&
147
+ !@config.javascript_safe_method?(call_node.method_name)
148
+
149
+ add_error(
150
+ "erb interpolation in javascript attribute must be wrapped in safe helper such as '(...).to_json'",
151
+ location: nested_location(parent_node, ruby_node)
152
+ )
150
153
  end
151
154
  end
152
155
 
@@ -1,43 +1,45 @@
1
- require 'better_html/parser'
2
- require 'better_html/test_helper/safety_error'
3
- require 'better_html/test_helper/safe_erb/base'
4
- require 'better_html/test_helper/safe_erb/no_statements'
5
- require 'better_html/test_helper/safe_erb/allowed_script_type'
6
- require 'better_html/test_helper/safe_erb/no_javascript_tag_helper'
7
- require 'better_html/test_helper/safe_erb/tag_interpolation'
8
- require 'better_html/test_helper/safe_erb/script_interpolation'
9
- require 'better_html/tree/tag'
1
+ # frozen_string_literal: true
2
+
3
+ require "better_html/parser"
4
+ require "better_html/test_helper/safety_error"
5
+ require "better_html/test_helper/safe_erb/base"
6
+ require "better_html/test_helper/safe_erb/no_statements"
7
+ require "better_html/test_helper/safe_erb/allowed_script_type"
8
+ require "better_html/test_helper/safe_erb/no_javascript_tag_helper"
9
+ require "better_html/test_helper/safe_erb/tag_interpolation"
10
+ require "better_html/test_helper/safe_erb/script_interpolation"
11
+ require "better_html/tree/tag"
10
12
 
11
13
  module BetterHtml
12
14
  module TestHelper
13
15
  module SafeErbTester
14
- SAFETY_TIPS = <<-EOF
15
- -----------
16
+ SAFETY_TIPS = <<~EOF
17
+ -----------
16
18
 
17
- The javascript snippets listed above do not appear to be escaped properly
18
- in a javascript context. Here are some tips:
19
+ The javascript snippets listed above do not appear to be escaped properly
20
+ in a javascript context. Here are some tips:
19
21
 
20
- Never use html_safe inside a html tag, since it is _never_ safe:
21
- <a href="<%= value.html_safe %>">
22
- ^^^^^^^^^^
22
+ Never use html_safe inside a html tag, since it is _never_ safe:
23
+ <a href="<%= value.html_safe %>">
24
+ ^^^^^^^^^^
23
25
 
24
- Always use .to_json for html attributes which contain javascript, like 'onclick',
25
- or twine attributes like 'data-define', 'data-context', 'data-eval', 'data-bind', etc:
26
- <div onclick="<%= value.to_json %>">
27
- ^^^^^^^^
26
+ Always use .to_json for html attributes which contain javascript, like 'onclick',
27
+ or twine attributes like 'data-define', 'data-context', 'data-eval', 'data-bind', etc:
28
+ <div onclick="<%= value.to_json %>">
29
+ ^^^^^^^^
28
30
 
29
- Always use raw and to_json together within <script> tags:
30
- <script type="text/javascript">
31
- var yourValue = <%= raw value.to_json %>;
32
- </script> ^^^ ^^^^^^^^
31
+ Always use raw and to_json together within <script> tags:
32
+ <script type="text/javascript">
33
+ var yourValue = <%= raw value.to_json %>;
34
+ </script> ^^^ ^^^^^^^^
33
35
 
34
- -----------
35
- EOF
36
+ -----------
37
+ EOF
36
38
 
37
39
  def assert_erb_safety(data, **options)
38
40
  options = options.present? ? options.dup : {}
39
41
  options[:template_language] ||= :html
40
- buffer = ::Parser::Source::Buffer.new(options[:filename] || '(buffer)')
42
+ buffer = ::Parser::Source::Buffer.new(options[:filename] || "(buffer)")
41
43
  buffer.source = data
42
44
  parser = BetterHtml::Parser.new(buffer, **options)
43
45
 
@@ -59,14 +61,14 @@ EOF
59
61
 
60
62
  messages = errors.map do |error|
61
63
  <<~EOL
62
- In #{buffer.name}:#{error.location.line}
63
- #{error.message}
64
- #{error.location.line_source_with_underline}\n
64
+ In #{buffer.name}:#{error.location.line}
65
+ #{error.message}
66
+ #{error.location.line_source_with_underline}\n
65
67
  EOL
66
68
  end
67
69
  messages << SAFETY_TIPS
68
70
 
69
- assert_predicate errors, :empty?, messages.join
71
+ assert_predicate(errors, :empty?, messages.join)
70
72
  end
71
73
  end
72
74
  end
@@ -1,35 +1,37 @@
1
- require 'better_html/test_helper/safety_error'
2
- require 'better_html/ast/iterator'
3
- require 'better_html/tree/tag'
4
- require 'better_html/parser'
1
+ # frozen_string_literal: true
2
+
3
+ require "better_html/test_helper/safety_error"
4
+ require "better_html/ast/iterator"
5
+ require "better_html/tree/tag"
6
+ require "better_html/parser"
5
7
 
6
8
  module BetterHtml
7
9
  module TestHelper
8
10
  module SafeLodashTester
9
- SAFETY_TIPS = <<-EOF
10
- -----------
11
+ SAFETY_TIPS = <<~EOF
12
+ -----------
11
13
 
12
- The javascript snippets listed above do not appear to be escaped properly
13
- in their context. Here are some tips:
14
+ The javascript snippets listed above do not appear to be escaped properly
15
+ in their context. Here are some tips:
14
16
 
15
- Always use lodash's escape syntax inside a html tag:
16
- <a href="[%= value %]">
17
- ^^^^
17
+ Always use lodash's escape syntax inside a html tag:
18
+ <a href="[%= value %]">
19
+ ^^^^
18
20
 
19
- Always use JSON.stringify() for html attributes which contain javascript, like 'onclick',
20
- or twine attributes like 'data-define', 'data-context', 'data-eval', 'data-bind', etc:
21
- <div onclick="[%= JSON.stringify(value) %]">
22
- ^^^^^^^^^^^^^^
21
+ Always use JSON.stringify() for html attributes which contain javascript, like 'onclick',
22
+ or twine attributes like 'data-define', 'data-context', 'data-eval', 'data-bind', etc:
23
+ <div onclick="[%= JSON.stringify(value) %]">
24
+ ^^^^^^^^^^^^^^
23
25
 
24
- Never use <script> tags inside lodash template.
25
- <script type="text/javascript">
26
- ^^^^^^^
26
+ Never use <script> tags inside lodash template.
27
+ <script type="text/javascript">
28
+ ^^^^^^^
27
29
 
28
- -----------
29
- EOF
30
+ -----------
31
+ EOF
30
32
 
31
33
  def assert_lodash_safety(data, **options)
32
- buffer = ::Parser::Source::Buffer.new(options[:filename] || '(buffer)')
34
+ buffer = ::Parser::Source::Buffer.new(options[:filename] || "(buffer)")
33
35
  buffer.source = data
34
36
  tester = Tester.new(buffer, **options)
35
37
 
@@ -44,11 +46,9 @@ EOF
44
46
 
45
47
  message << SAFETY_TIPS
46
48
 
47
- assert_predicate tester.errors, :empty?, message
49
+ assert_predicate(tester.errors, :empty?, message)
48
50
  end
49
51
 
50
- private
51
-
52
52
  class Tester
53
53
  attr_reader :errors
54
54
 
@@ -70,12 +70,12 @@ EOF
70
70
  validate_tag_attributes(tag)
71
71
  validate_no_statements(tag_node)
72
72
 
73
- if tag.name == 'script' && !tag.closing?
74
- add_error(
75
- "No script tags allowed nested in lodash templates",
76
- location: tag_node.loc
77
- )
78
- end
73
+ next unless tag.name == "script" && !tag.closing?
74
+
75
+ add_error(
76
+ "No script tags allowed nested in lodash templates",
77
+ location: tag_node.loc
78
+ )
79
79
  end
80
80
 
81
81
  @parser.nodes_with_type(:cdata, :comment).each do |node|
@@ -86,6 +86,7 @@ EOF
86
86
  def lodash_nodes(node)
87
87
  Enumerator.new do |yielder|
88
88
  next if node.nil?
89
+
89
90
  node.descendants(:lodash).each do |lodash_node|
90
91
  indicator_node, code_node = *lodash_node
91
92
  yielder.yield(lodash_node, indicator_node, code_node)
@@ -95,12 +96,12 @@ EOF
95
96
 
96
97
  def validate_tag_attributes(tag)
97
98
  tag.attributes.each do |attribute|
98
- lodash_nodes(attribute.value_node).each do |lodash_node, indicator_node, code_node|
99
+ lodash_nodes(attribute.value_node).each do |lodash_node, indicator_node, _code_node|
99
100
  next if indicator_node.nil?
100
101
 
101
- if indicator_node.loc.source == '='
102
+ if indicator_node.loc.source == "="
102
103
  validate_tag_expression(attribute, lodash_node)
103
- elsif indicator_node.loc.source == '!'
104
+ elsif indicator_node.loc.source == "!"
104
105
  add_error(
105
106
  "lodash interpolation with '[%!' inside html attribute is never safe",
106
107
  location: lodash_node.loc
@@ -116,14 +117,14 @@ EOF
116
117
  if @config.javascript_attribute_name?(attribute.name) && !@config.lodash_safe_javascript_expression?(source)
117
118
  add_error(
118
119
  "lodash interpolation in javascript attribute "\
119
- "`#{attribute.name}` must call `JSON.stringify(#{source})`",
120
+ "`#{attribute.name}` must call `JSON.stringify(#{source})`",
120
121
  location: lodash_node.loc
121
122
  )
122
123
  end
123
124
  end
124
125
 
125
126
  def validate_no_statements(node)
126
- lodash_nodes(node).each do |lodash_node, indicator_node, code_node|
127
+ lodash_nodes(node).each do |lodash_node, indicator_node, _code_node|
127
128
  add_no_statement_error(lodash_node.loc) if indicator_node.nil?
128
129
  end
129
130
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module BetterHtml
2
4
  module TestHelper
3
5
  class SafetyError < InterpolatorError
@@ -1,7 +1,9 @@
1
- require 'erubi'
2
- require_relative 'token'
3
- require_relative 'location'
4
- require 'parser/source/buffer'
1
+ # frozen_string_literal: true
2
+
3
+ require "erubi"
4
+ require_relative "token"
5
+ require_relative "location"
6
+ require "parser/source/buffer"
5
7
 
6
8
  module BetterHtml
7
9
  module Tokenizer
@@ -14,7 +16,9 @@ module BetterHtml
14
16
  attr_reader :current_position
15
17
 
16
18
  def initialize(buffer)
17
- raise ArgumentError, 'first argument must be Parser::Source::Buffer' unless buffer.is_a?(::Parser::Source::Buffer)
19
+ raise ArgumentError,
20
+ "first argument must be Parser::Source::Buffer" unless buffer.is_a?(::Parser::Source::Buffer)
21
+
18
22
  @buffer = buffer
19
23
  @tokens = []
20
24
  @current_position = 0
@@ -28,13 +32,13 @@ module BetterHtml
28
32
  end
29
33
 
30
34
  def add_code(code)
31
- if code[0] == '%'
32
- add_erb_tokens(nil, '%', code[1..-1], nil)
35
+ if code[0] == "%"
36
+ add_erb_tokens(nil, "%", code[1..-1], nil)
33
37
  append("<%#{code}%>")
34
38
  else
35
39
  _, ltrim_or_comment, code, rtrim = *STMT_TRIM_MATCHER.match(code)
36
- ltrim = ltrim_or_comment if ltrim_or_comment == '-'
37
- indicator = ltrim_or_comment if ltrim_or_comment == '#'
40
+ ltrim = ltrim_or_comment if ltrim_or_comment == "-"
41
+ indicator = ltrim_or_comment if ltrim_or_comment == "#"
38
42
  add_erb_tokens(ltrim, indicator, code, rtrim)
39
43
  append("<%#{ltrim}#{indicator}#{code}#{rtrim}%>")
40
44
  end
@@ -66,7 +70,7 @@ module BetterHtml
66
70
  pos += code.length
67
71
 
68
72
  if rtrim
69
- token = add_token(:trim, pos, pos + rtrim.length)
73
+ add_token(:trim, pos, pos + rtrim.length)
70
74
  pos += rtrim.length
71
75
  end
72
76
 
@@ -1,5 +1,6 @@
1
- require 'html_tokenizer'
2
- require_relative 'base_erb'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_erb"
3
4
 
4
5
  module BetterHtml
5
6
  module Tokenizer
@@ -1,7 +1,8 @@
1
- require 'active_support'
2
- require 'html_tokenizer'
3
- require_relative 'token'
4
- require_relative 'location'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require_relative "token"
5
+ require_relative "location"
5
6
 
6
7
  module BetterHtml
7
8
  module Tokenizer
@@ -10,9 +11,9 @@ module BetterHtml
10
11
  attr_reader :parser
11
12
 
12
13
  cattr_accessor :lodash_escape, :lodash_evaluate, :lodash_interpolate
13
- self.lodash_escape = %r{(?:\[\%)=(.+?)(?:\%\])}m
14
- self.lodash_evaluate = %r{(?:\[\%)(.+?)(?:\%\])}m
15
- self.lodash_interpolate = %r{(?:\[\%)!(.+?)(?:\%\])}m
14
+ self.lodash_escape = /(?:\[\%)=(.+?)(?:\%\])/m
15
+ self.lodash_evaluate = /(?:\[\%)(.+?)(?:\%\])/m
16
+ self.lodash_interpolate = /(?:\[\%)!(.+?)(?:\%\])/m
16
17
 
17
18
  def initialize(buffer)
18
19
  @buffer = buffer
@@ -27,25 +28,32 @@ module BetterHtml
27
28
  def scan!
28
29
  while @scanner.rest?
29
30
  scanned = @scanner.scan_until(scan_pattern)
31
+
30
32
  if scanned.present?
31
33
  captures = scan_pattern.match(scanned).captures
32
- if pre_match = captures[0]
33
- add_text(pre_match) if pre_match.present?
34
+
35
+ if (pre_match = captures[0])
36
+ add_text(pre_match) if pre_match.present? # rubocop:disable Metrics/BlockNesting
34
37
  end
38
+
35
39
  match = captures[1]
36
- if code = lodash_escape.match(match)
40
+
41
+ if (code = lodash_escape.match(match))
37
42
  add_lodash_tokens("=", code.captures[0])
38
- elsif code = lodash_interpolate.match(match)
43
+ elsif (code = lodash_interpolate.match(match))
39
44
  add_lodash_tokens("!", code.captures[0])
40
- elsif code = lodash_evaluate.match(match)
45
+ elsif (code = lodash_evaluate.match(match))
41
46
  add_lodash_tokens(nil, code.captures[0])
42
47
  else
43
- raise RuntimeError, 'unexpected match'
48
+ raise "unexpected match"
44
49
  end
50
+
45
51
  @parser.append_placeholder(match)
46
52
  else
47
53
  text = @buffer.source[(@scanner.pos)..(@buffer.source.size)]
54
+
48
55
  add_text(text) unless text.blank?
56
+
49
57
  break
50
58
  end
51
59
  end
@@ -56,7 +64,7 @@ module BetterHtml
56
64
  patterns = [
57
65
  lodash_escape,
58
66
  lodash_interpolate,
59
- lodash_evaluate
67
+ lodash_evaluate,
60
68
  ].map(&:source).join("|")
61
69
  Regexp.new("(?<pre_patch>.*?)(?<match>" + patterns + ")", Regexp::MULTILINE)
62
70
  end
@@ -1,4 +1,6 @@
1
- require_relative 'base_erb'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_erb"
2
4
 
3
5
  module BetterHtml
4
6
  module Tokenizer
@@ -1,13 +1,24 @@
1
- require 'parser/source/buffer'
2
- require 'parser/source/range'
1
+ # frozen_string_literal: true
2
+
3
+ require "parser/source/buffer"
4
+ require "parser/source/range"
3
5
 
4
6
  module BetterHtml
5
7
  module Tokenizer
6
8
  class Location < ::Parser::Source::Range
7
9
  def initialize(buffer, begin_pos, end_pos)
8
- raise ArgumentError, 'first argument must be Parser::Source::Buffer' unless buffer.is_a?(::Parser::Source::Buffer)
9
- raise ArgumentError, "begin_pos location #{begin_pos} is out of range for document of size #{buffer.source.size}" if begin_pos > buffer.source.size
10
- raise ArgumentError, "end_pos location #{end_pos} is out of range for document of size #{buffer.source.size}" if (end_pos - 1) > buffer.source.size
10
+ raise ArgumentError,
11
+ "first argument must be Parser::Source::Buffer" unless buffer.is_a?(::Parser::Source::Buffer)
12
+
13
+ if begin_pos > buffer.source.size
14
+ raise ArgumentError,
15
+ "begin_pos location #{begin_pos} is out of range for document of size #{buffer.source.size}"
16
+ end
17
+
18
+ if (end_pos - 1) > buffer.source.size
19
+ raise ArgumentError,
20
+ "end_pos location #{end_pos} is out of range for document of size #{buffer.source.size}"
21
+ end
11
22
 
12
23
  super(buffer, begin_pos, end_pos)
13
24
  end
@@ -29,7 +40,7 @@ module BetterHtml
29
40
  spaces = source_line.scan(/\A\s*/).first
30
41
  column_without_spaces = [column - spaces.length, 0].max
31
42
  underscore_length = [[end_pos - begin_pos, source_line.length - column_without_spaces].min, 1].max
32
- "#{source_line.gsub(/\A\s*/, '')}\n#{' ' * column_without_spaces}#{'^' * underscore_length}"
43
+ "#{source_line.gsub(/\A\s*/, "")}\n#{" " * column_without_spaces}#{"^" * underscore_length}"
33
44
  end
34
45
 
35
46
  def with(begin_pos: @begin_pos, end_pos: @end_pos)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module BetterHtml
2
4
  module Tokenizer
3
5
  class Token
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module BetterHtml
2
4
  module Tokenizer
3
5
  class TokenArray
@@ -8,26 +10,24 @@ module BetterHtml
8
10
  end
9
11
 
10
12
  def shift
11
- raise RuntimeError, 'no tokens left to shift' if empty?
13
+ raise "no tokens left to shift" if empty?
14
+
12
15
  item = @list[@current]
13
16
  @current += 1
14
17
  item
15
18
  end
16
19
 
17
20
  def pop
18
- raise RuntimeError, 'no tokens left to pop' if empty?
21
+ raise "no tokens left to pop" if empty?
22
+
19
23
  item = @list[@last - 1]
20
24
  @last -= 1
21
25
  item
22
26
  end
23
27
 
24
28
  def trim(type)
25
- while current&.type == type
26
- shift
27
- end
28
- while last&.type == type
29
- pop
30
- end
29
+ shift while current&.type == type
30
+ pop while last&.type == type
31
31
  end
32
32
 
33
33
  def empty?
@@ -1,19 +1,23 @@
1
- require 'ast'
1
+ # frozen_string_literal: true
2
+
3
+ require "ast"
2
4
 
3
5
  module BetterHtml
4
6
  module Tree
5
7
  class Attribute
6
8
  attr_reader :node, :name_node, :equal_node, :value_node
7
9
 
10
+ class << self
11
+ def from_node(node)
12
+ new(node)
13
+ end
14
+ end
15
+
8
16
  def initialize(node)
9
17
  @node = node
10
18
  @name_node, @equal_node, @value_node = *node if @node.type == :attribute
11
19
  end
12
20
 
13
- def self.from_node(node)
14
- new(node)
15
- end
16
-
17
21
  def erb?
18
22
  @node.type == :erb
19
23
  end
@@ -27,7 +31,7 @@ module BetterHtml
27
31
  end
28
32
 
29
33
  def value
30
- parts = value_node.to_a.reject{ |node| node.is_a?(::AST::Node) && node.type == :quote }
34
+ parts = value_node.to_a.reject { |node| node.is_a?(::AST::Node) && node.type == :quote }
31
35
  parts.map { |s| s.is_a?(::AST::Node) ? s.loc.source : CGI.unescapeHTML(s) }.join
32
36
  end
33
37
  end
@@ -1,14 +1,18 @@
1
- require 'better_html/tree/attribute'
1
+ # frozen_string_literal: true
2
+
3
+ require "better_html/tree/attribute"
2
4
 
3
5
  module BetterHtml
4
6
  module Tree
5
7
  class AttributesList
6
- def initialize(list)
7
- @list = list || []
8
+ class << self
9
+ def from_nodes(nodes)
10
+ new(nodes&.map { |node| Tree::Attribute.from_node(node) })
11
+ end
8
12
  end
9
13
 
10
- def self.from_nodes(nodes)
11
- new(nodes&.map { |node| Tree::Attribute.from_node(node) })
14
+ def initialize(list)
15
+ @list = list || []
12
16
  end
13
17
 
14
18
  def [](name)