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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -0
- data/Rakefile +19 -14
- data/ext/better_html_ext/better_html.h +1 -0
- data/ext/better_html_ext/extconf.rb +16 -0
- data/ext/better_html_ext/html_tokenizer.c +12 -0
- data/ext/better_html_ext/html_tokenizer.h +7 -0
- data/ext/better_html_ext/parser.c +793 -0
- data/ext/better_html_ext/parser.h +93 -0
- data/ext/better_html_ext/tokenizer.c +717 -0
- data/ext/better_html_ext/tokenizer.h +80 -0
- data/lib/better_html/ast/iterator.rb +14 -9
- data/lib/better_html/ast/node.rb +4 -2
- data/lib/better_html/better_erb/erubi_implementation.rb +43 -39
- data/lib/better_html/better_erb/runtime_checks.rb +140 -133
- data/lib/better_html/better_erb/validated_output_buffer.rb +30 -22
- data/lib/better_html/better_erb.rb +58 -54
- data/lib/better_html/config.rb +7 -4
- data/lib/better_html/errors.rb +4 -2
- data/lib/better_html/helpers.rb +7 -3
- data/lib/better_html/html_attributes.rb +6 -2
- data/lib/better_html/parser.rb +21 -14
- data/lib/better_html/railtie.rb +8 -4
- data/lib/better_html/test_helper/ruby_node.rb +15 -10
- data/lib/better_html/test_helper/safe_erb/allowed_script_type.rb +8 -4
- data/lib/better_html/test_helper/safe_erb/base.rb +12 -9
- data/lib/better_html/test_helper/safe_erb/no_javascript_tag_helper.rb +7 -3
- data/lib/better_html/test_helper/safe_erb/no_statements.rb +7 -3
- data/lib/better_html/test_helper/safe_erb/script_interpolation.rb +9 -4
- data/lib/better_html/test_helper/safe_erb/tag_interpolation.rb +23 -20
- data/lib/better_html/test_helper/safe_erb_tester.rb +33 -31
- data/lib/better_html/test_helper/safe_lodash_tester.rb +36 -35
- data/lib/better_html/test_helper/safety_error.rb +2 -0
- data/lib/better_html/tokenizer/base_erb.rb +14 -10
- data/lib/better_html/tokenizer/html_erb.rb +3 -2
- data/lib/better_html/tokenizer/html_lodash.rb +22 -14
- data/lib/better_html/tokenizer/javascript_erb.rb +3 -1
- data/lib/better_html/tokenizer/location.rb +17 -6
- data/lib/better_html/tokenizer/token.rb +2 -0
- data/lib/better_html/tokenizer/token_array.rb +8 -8
- data/lib/better_html/tree/attribute.rb +10 -6
- data/lib/better_html/tree/attributes_list.rb +9 -5
- data/lib/better_html/tree/tag.rb +10 -6
- data/lib/better_html/version.rb +3 -1
- data/lib/better_html.rb +19 -17
- data/lib/tasks/better_html_tasks.rake +1 -0
- metadata +39 -147
- data/lib/better_html/better_erb/erubis_implementation.rb +0 -44
- data/test/better_html/better_erb/implementation_test.rb +0 -406
- data/test/better_html/errors_test.rb +0 -13
- data/test/better_html/helpers_test.rb +0 -49
- data/test/better_html/parser_test.rb +0 -314
- data/test/better_html/test_helper/ruby_node_test.rb +0 -288
- data/test/better_html/test_helper/safe_erb/allowed_script_type_test.rb +0 -46
- data/test/better_html/test_helper/safe_erb/no_javascript_tag_helper_test.rb +0 -37
- data/test/better_html/test_helper/safe_erb/no_statements_test.rb +0 -129
- data/test/better_html/test_helper/safe_erb/script_interpolation_test.rb +0 -149
- data/test/better_html/test_helper/safe_erb/tag_interpolation_test.rb +0 -303
- data/test/better_html/test_helper/safe_lodash_tester_test.rb +0 -90
- data/test/better_html/tokenizer/html_erb_test.rb +0 -180
- data/test/better_html/tokenizer/html_lodash_test.rb +0 -98
- data/test/better_html/tokenizer/location_test.rb +0 -75
- data/test/better_html/tokenizer/token_array_test.rb +0 -146
- data/test/better_html/tokenizer/token_test.rb +0 -15
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/controllers/application_controller.rb +0 -5
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/bin/setup +0 -29
- data/test/dummy/config/application.rb +0 -26
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/database.yml +0 -25
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -41
- data/test/dummy/config/environments/production.rb +0 -79
- data/test/dummy/config/environments/test.rb +0 -42
- data/test/dummy/config/initializers/assets.rb +0 -11
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -4
- data/test/dummy/config/initializers/session_store.rb +0 -3
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -23
- data/test/dummy/config/routes.rb +0 -56
- data/test/dummy/config/secrets.yml +0 -22
- data/test/dummy/config.ru +0 -4
- data/test/dummy/public/404.html +0 -67
- data/test/dummy/public/422.html +0 -67
- data/test/dummy/public/500.html +0 -66
- data/test/dummy/public/favicon.ico +0 -0
- data/test/test_helper.rb +0 -29
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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[
|
|
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 ==
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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 |
|
|
67
|
+
erb_nodes(text_node).each do |_erb_node, indicator_node, code_node|
|
|
69
68
|
indicator = indicator_node&.loc&.source
|
|
70
|
-
next if 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
|
-
|
|
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 ==
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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] ||
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
71
|
+
assert_predicate(errors, :empty?, messages.join)
|
|
70
72
|
end
|
|
71
73
|
end
|
|
72
74
|
end
|
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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] ||
|
|
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
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
73
|
+
add_token(:trim, pos, pos + rtrim.length)
|
|
70
74
|
pos += rtrim.length
|
|
71
75
|
end
|
|
72
76
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
require_relative
|
|
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 =
|
|
14
|
-
self.lodash_evaluate =
|
|
15
|
-
self.lodash_interpolate =
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
|
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,13 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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,
|
|
9
|
-
|
|
10
|
-
|
|
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*/,
|
|
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 TokenArray
|
|
@@ -8,26 +10,24 @@ module BetterHtml
|
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def shift
|
|
11
|
-
raise
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
|
11
|
-
|
|
14
|
+
def initialize(list)
|
|
15
|
+
@list = list || []
|
|
12
16
|
end
|
|
13
17
|
|
|
14
18
|
def [](name)
|