better_html 0.0.12 → 1.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/lib/better_html.rb +0 -2
- data/lib/better_html/ast/iterator.rb +32 -0
- data/lib/better_html/ast/node.rb +14 -0
- data/lib/better_html/better_erb/runtime_checks.rb +3 -3
- data/lib/better_html/config.rb +12 -0
- data/lib/better_html/parser.rb +286 -0
- data/lib/better_html/test_helper/ruby_expr.rb +8 -5
- data/lib/better_html/test_helper/safe_erb_tester.rb +121 -108
- data/lib/better_html/test_helper/safe_lodash_tester.rb +44 -42
- data/lib/better_html/tokenizer/base_erb.rb +79 -0
- data/lib/better_html/tokenizer/html_erb.rb +31 -0
- data/lib/better_html/{node_iterator → tokenizer}/html_lodash.rb +30 -34
- data/lib/better_html/tokenizer/javascript_erb.rb +15 -0
- data/lib/better_html/{node_iterator → tokenizer}/location.rb +9 -3
- data/lib/better_html/tokenizer/token.rb +16 -0
- data/lib/better_html/tokenizer/token_array.rb +54 -0
- data/lib/better_html/tree/attribute.rb +31 -0
- data/lib/better_html/tree/attributes_list.rb +25 -0
- data/lib/better_html/tree/tag.rb +39 -0
- data/lib/better_html/version.rb +1 -1
- data/test/better_html/parser_test.rb +279 -0
- data/test/better_html/test_helper/safe_erb_tester_test.rb +11 -0
- data/test/better_html/test_helper/safe_lodash_tester_test.rb +11 -1
- data/test/better_html/tokenizer/html_erb_test.rb +158 -0
- data/test/better_html/tokenizer/html_lodash_test.rb +98 -0
- data/test/better_html/tokenizer/location_test.rb +57 -0
- data/test/better_html/tokenizer/token_array_test.rb +144 -0
- data/test/better_html/tokenizer/token_test.rb +15 -0
- metadata +45 -30
- data/lib/better_html/node_iterator.rb +0 -144
- data/lib/better_html/node_iterator/attribute.rb +0 -34
- data/lib/better_html/node_iterator/base.rb +0 -27
- data/lib/better_html/node_iterator/cdata.rb +0 -8
- data/lib/better_html/node_iterator/comment.rb +0 -8
- data/lib/better_html/node_iterator/content_node.rb +0 -13
- data/lib/better_html/node_iterator/element.rb +0 -26
- data/lib/better_html/node_iterator/html_erb.rb +0 -70
- data/lib/better_html/node_iterator/javascript_erb.rb +0 -55
- data/lib/better_html/node_iterator/text.rb +0 -8
- data/lib/better_html/node_iterator/token.rb +0 -8
- data/lib/better_html/tree.rb +0 -113
- data/test/better_html/node_iterator/html_erb_test.rb +0 -116
- data/test/better_html/node_iterator/html_lodash_test.rb +0 -132
- data/test/better_html/node_iterator/location_test.rb +0 -36
- data/test/better_html/node_iterator_test.rb +0 -221
- data/test/better_html/tree_test.rb +0 -110
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'ast'
|
2
|
+
|
3
|
+
module BetterHtml
|
4
|
+
module Tree
|
5
|
+
class Attribute
|
6
|
+
attr_reader :node, :name_node, :equal_node, :value_node
|
7
|
+
|
8
|
+
def initialize(node)
|
9
|
+
@node = node
|
10
|
+
@name_node, @equal_node, @value_node = *node
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_node(node)
|
14
|
+
new(node)
|
15
|
+
end
|
16
|
+
|
17
|
+
def loc
|
18
|
+
@node.loc
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
@name_node&.loc&.source&.downcase
|
23
|
+
end
|
24
|
+
|
25
|
+
def value
|
26
|
+
parts = value_node.to_a.reject{ |node| node.is_a?(::AST::Node) && node.type == :quote }
|
27
|
+
parts.map { |s| s.is_a?(::AST::Node) ? s.loc.source : CGI.unescapeHTML(s) }.join
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'better_html/tree/attribute'
|
2
|
+
|
3
|
+
module BetterHtml
|
4
|
+
module Tree
|
5
|
+
class AttributesList
|
6
|
+
def initialize(list)
|
7
|
+
@list = list || []
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_nodes(nodes)
|
11
|
+
new(nodes&.map { |node| Tree::Attribute.from_node(node) })
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](name)
|
15
|
+
@list.find do |attribute|
|
16
|
+
attribute.name == name.downcase
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
@list.each(&block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'better_html/tree/attributes_list'
|
2
|
+
require 'better_html/ast/iterator'
|
3
|
+
|
4
|
+
module BetterHtml
|
5
|
+
module Tree
|
6
|
+
class Tag
|
7
|
+
attr_reader :node
|
8
|
+
|
9
|
+
def initialize(node)
|
10
|
+
@node = node
|
11
|
+
@start_solidus, @name_node, @attributes_node, @end_solidus = *node
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from_node(node)
|
15
|
+
new(node)
|
16
|
+
end
|
17
|
+
|
18
|
+
def loc
|
19
|
+
@node.loc
|
20
|
+
end
|
21
|
+
|
22
|
+
def name
|
23
|
+
@name_node&.loc&.source&.downcase
|
24
|
+
end
|
25
|
+
|
26
|
+
def closing?
|
27
|
+
@start_solidus&.type == :solidus
|
28
|
+
end
|
29
|
+
|
30
|
+
def self_closing?
|
31
|
+
@end_solidus&.type == :solidus
|
32
|
+
end
|
33
|
+
|
34
|
+
def attributes
|
35
|
+
@attributes ||= AttributesList.from_nodes(@attributes_node.to_a)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/better_html/version.rb
CHANGED
@@ -0,0 +1,279 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'better_html/parser'
|
3
|
+
require 'ast'
|
4
|
+
|
5
|
+
module BetterHtml
|
6
|
+
class ParserTest < ActiveSupport::TestCase
|
7
|
+
include ::AST::Sexp
|
8
|
+
|
9
|
+
test "parse empty document" do
|
10
|
+
tree = Parser.new('')
|
11
|
+
|
12
|
+
assert_equal s(:document), tree.ast
|
13
|
+
end
|
14
|
+
|
15
|
+
test "consume cdata nodes" do
|
16
|
+
code = "<![CDATA[ foo ]]>"
|
17
|
+
tree = Parser.new(code)
|
18
|
+
|
19
|
+
assert_equal s(:document, s(:cdata, ' foo ')), tree.ast
|
20
|
+
assert_equal code, tree.ast.loc.source
|
21
|
+
end
|
22
|
+
|
23
|
+
test "unterminated cdata nodes are consumed until end" do
|
24
|
+
code = "<![CDATA[ foo"
|
25
|
+
tree = Parser.new(code)
|
26
|
+
|
27
|
+
assert_equal s(:document, s(:cdata, ' foo')), tree.ast
|
28
|
+
assert_equal code, tree.ast.loc.source
|
29
|
+
end
|
30
|
+
|
31
|
+
test "consume cdata with interpolation" do
|
32
|
+
code = "<![CDATA[ foo <%= bar %> baz ]]>"
|
33
|
+
tree = Parser.new(code)
|
34
|
+
|
35
|
+
assert_equal s(:document,
|
36
|
+
s(:cdata,
|
37
|
+
" foo ",
|
38
|
+
s(:erb, s(:indicator, '='), nil, s(:code, " bar "), nil),
|
39
|
+
" baz "
|
40
|
+
)),
|
41
|
+
tree.ast
|
42
|
+
assert_equal code, tree.ast.loc.source
|
43
|
+
end
|
44
|
+
|
45
|
+
test "consume comment nodes" do
|
46
|
+
tree = Parser.new("<!-- foo -->")
|
47
|
+
|
48
|
+
assert_equal s(:document, s(:comment, ' foo ')), tree.ast
|
49
|
+
end
|
50
|
+
|
51
|
+
test "unterminated comment nodes are consumed until end" do
|
52
|
+
tree = Parser.new("<!-- foo")
|
53
|
+
|
54
|
+
assert_equal s(:document, s(:comment, ' foo')), tree.ast
|
55
|
+
end
|
56
|
+
|
57
|
+
test "consume comment with interpolation" do
|
58
|
+
tree = Parser.new("<!-- foo <%= bar %> baz -->")
|
59
|
+
|
60
|
+
assert_equal s(:document,
|
61
|
+
s(:comment,
|
62
|
+
" foo ",
|
63
|
+
s(:erb, s(:indicator, "="), nil, s(:code, " bar "), nil),
|
64
|
+
" baz "
|
65
|
+
)),
|
66
|
+
tree.ast
|
67
|
+
end
|
68
|
+
|
69
|
+
test "consume tag nodes" do
|
70
|
+
tree = Parser.new("<div>")
|
71
|
+
assert_equal s(:document, s(:tag, nil, s(:tag_name, "div"), nil, nil)), tree.ast
|
72
|
+
end
|
73
|
+
|
74
|
+
test "tag without name" do
|
75
|
+
tree = Parser.new("foo < bar")
|
76
|
+
assert_equal s(:document,
|
77
|
+
s(:text, "foo "),
|
78
|
+
s(:tag, nil, nil,
|
79
|
+
s(:tag_attributes,
|
80
|
+
s(:attribute, s(:attribute_name, 'bar'), nil, nil)
|
81
|
+
),
|
82
|
+
nil
|
83
|
+
)
|
84
|
+
), tree.ast
|
85
|
+
end
|
86
|
+
|
87
|
+
test "consume tag nodes with solidus" do
|
88
|
+
tree = Parser.new("</div>")
|
89
|
+
assert_equal s(:document, s(:tag, s(:solidus), s(:tag_name, "div"), nil, nil)), tree.ast
|
90
|
+
end
|
91
|
+
|
92
|
+
test "sets self_closing when appropriate" do
|
93
|
+
tree = Parser.new("<div/>")
|
94
|
+
assert_equal s(:document, s(:tag, nil, s(:tag_name, "div"), nil, s(:solidus))), tree.ast
|
95
|
+
end
|
96
|
+
|
97
|
+
test "consume tag nodes until name ends" do
|
98
|
+
tree = Parser.new("<div/>")
|
99
|
+
assert_equal s(:document, s(:tag, nil, s(:tag_name, "div"), nil, s(:solidus))), tree.ast
|
100
|
+
|
101
|
+
tree = Parser.new("<div ")
|
102
|
+
assert_equal s(:document, s(:tag, nil, s(:tag_name, "div"), nil, nil)), tree.ast
|
103
|
+
end
|
104
|
+
|
105
|
+
test "consume tag nodes with interpolation" do
|
106
|
+
tree = Parser.new("<ns:<%= name %>-thing>")
|
107
|
+
assert_equal s(:document,
|
108
|
+
s(:tag,
|
109
|
+
nil,
|
110
|
+
s(:tag_name, "ns:", s(:erb, s(:indicator, "="), nil, s(:code, " name "), nil), "-thing"),
|
111
|
+
nil,
|
112
|
+
nil
|
113
|
+
)), tree.ast
|
114
|
+
end
|
115
|
+
|
116
|
+
test "consume tag attributes nodes unquoted value" do
|
117
|
+
tree = Parser.new("<div foo=bar>")
|
118
|
+
assert_equal s(:document,
|
119
|
+
s(:tag, nil,
|
120
|
+
s(:tag_name, "div"),
|
121
|
+
s(:tag_attributes,
|
122
|
+
s(:attribute,
|
123
|
+
s(:attribute_name, "foo"),
|
124
|
+
s(:equal),
|
125
|
+
s(:attribute_value, "bar")
|
126
|
+
)
|
127
|
+
),
|
128
|
+
nil
|
129
|
+
)), tree.ast
|
130
|
+
end
|
131
|
+
|
132
|
+
test "consume attributes without name" do
|
133
|
+
tree = Parser.new("<div 'thing'>")
|
134
|
+
assert_equal s(:document,
|
135
|
+
s(:tag, nil,
|
136
|
+
s(:tag_name, "div"),
|
137
|
+
s(:tag_attributes,
|
138
|
+
s(:attribute,
|
139
|
+
nil,
|
140
|
+
nil,
|
141
|
+
s(:attribute_value, s(:quote, "'"), "thing", s(:quote, "'"))
|
142
|
+
)
|
143
|
+
),
|
144
|
+
nil
|
145
|
+
)), tree.ast
|
146
|
+
end
|
147
|
+
|
148
|
+
test "consume tag attributes nodes quoted value" do
|
149
|
+
tree = Parser.new("<div foo=\"bar\">")
|
150
|
+
assert_equal s(:document,
|
151
|
+
s(:tag, nil,
|
152
|
+
s(:tag_name, "div"),
|
153
|
+
s(:tag_attributes,
|
154
|
+
s(:attribute,
|
155
|
+
s(:attribute_name, "foo"),
|
156
|
+
s(:equal),
|
157
|
+
s(:attribute_value, s(:quote, "\""), "bar", s(:quote, "\""))
|
158
|
+
)
|
159
|
+
),
|
160
|
+
nil
|
161
|
+
)), tree.ast
|
162
|
+
end
|
163
|
+
|
164
|
+
test "consume tag attributes nodes interpolation in name and value" do
|
165
|
+
tree = Parser.new("<div data-<%= foo %>=\"some <%= value %> foo\">")
|
166
|
+
assert_equal s(:document,
|
167
|
+
s(:tag, nil,
|
168
|
+
s(:tag_name, "div"),
|
169
|
+
s(:tag_attributes,
|
170
|
+
s(:attribute,
|
171
|
+
s(:attribute_name, "data-", s(:erb, s(:indicator, "="), nil, s(:code, " foo "), nil)),
|
172
|
+
s(:equal),
|
173
|
+
s(:attribute_value,
|
174
|
+
s(:quote, "\""),
|
175
|
+
"some ",
|
176
|
+
s(:erb, s(:indicator, "="), nil, s(:code, " value "), nil),
|
177
|
+
" foo",
|
178
|
+
s(:quote, "\""),
|
179
|
+
),
|
180
|
+
)
|
181
|
+
),
|
182
|
+
nil
|
183
|
+
)), tree.ast
|
184
|
+
end
|
185
|
+
|
186
|
+
test "consume text nodes" do
|
187
|
+
tree = Parser.new("here is <%= some %> text")
|
188
|
+
|
189
|
+
assert_equal s(:document,
|
190
|
+
s(:text,
|
191
|
+
"here is ",
|
192
|
+
s(:erb, s(:indicator, "="), nil, s(:code, " some "), nil),
|
193
|
+
" text"
|
194
|
+
)), tree.ast
|
195
|
+
end
|
196
|
+
|
197
|
+
test "javascript template parsing works" do
|
198
|
+
tree = Parser.new("here is <%= some %> text", template_language: :javascript)
|
199
|
+
|
200
|
+
assert_equal s(:document,
|
201
|
+
s(:text,
|
202
|
+
"here is ",
|
203
|
+
s(:erb, s(:indicator, "="), nil, s(:code, " some "), nil),
|
204
|
+
" text"
|
205
|
+
)), tree.ast
|
206
|
+
end
|
207
|
+
|
208
|
+
test "javascript template does not consume html tags" do
|
209
|
+
tree = Parser.new("<div <%= some %> />", template_language: :javascript)
|
210
|
+
|
211
|
+
assert_equal s(:document,
|
212
|
+
s(:text,
|
213
|
+
"<div ",
|
214
|
+
s(:erb, s(:indicator, "="), nil, s(:code, " some "), nil),
|
215
|
+
" />"
|
216
|
+
)), tree.ast
|
217
|
+
end
|
218
|
+
|
219
|
+
test "lodash template parsing works" do
|
220
|
+
tree = Parser.new('<div class="[%= foo %]">', template_language: :lodash)
|
221
|
+
|
222
|
+
assert_equal s(:document,
|
223
|
+
s(:tag,
|
224
|
+
nil,
|
225
|
+
s(:tag_name, "div"),
|
226
|
+
s(:tag_attributes,
|
227
|
+
s(:attribute,
|
228
|
+
s(:attribute_name, "class"),
|
229
|
+
s(:equal),
|
230
|
+
s(:attribute_value,
|
231
|
+
s(:quote, "\""),
|
232
|
+
s(:lodash, s(:indicator, "="), s(:code, " foo ")),
|
233
|
+
s(:quote, "\"")
|
234
|
+
)
|
235
|
+
)
|
236
|
+
),
|
237
|
+
nil
|
238
|
+
)
|
239
|
+
), tree.ast
|
240
|
+
end
|
241
|
+
|
242
|
+
test "nodes are all nested under document" do
|
243
|
+
tree = Parser.new(<<~HTML)
|
244
|
+
some text
|
245
|
+
<!-- a comment -->
|
246
|
+
some more text
|
247
|
+
<%= an erb tag -%>
|
248
|
+
<div class="foo">
|
249
|
+
content
|
250
|
+
</div>
|
251
|
+
HTML
|
252
|
+
|
253
|
+
assert_equal s(:document,
|
254
|
+
s(:text, "some text\n"),
|
255
|
+
s(:comment, " a comment "),
|
256
|
+
s(:text,
|
257
|
+
"\nsome more text\n",
|
258
|
+
s(:erb, s(:indicator, '='), nil, s(:code, ' an erb tag '), s(:trim)),
|
259
|
+
"\n"
|
260
|
+
),
|
261
|
+
s(:tag,
|
262
|
+
nil,
|
263
|
+
s(:tag_name, "div"),
|
264
|
+
s(:tag_attributes,
|
265
|
+
s(:attribute,
|
266
|
+
s(:attribute_name, "class"),
|
267
|
+
s(:equal),
|
268
|
+
s(:attribute_value, s(:quote, "\""), "foo", s(:quote, "\""))
|
269
|
+
)
|
270
|
+
),
|
271
|
+
nil
|
272
|
+
),
|
273
|
+
s(:text, "\n content\n"),
|
274
|
+
s(:tag, s(:solidus), s(:tag_name, 'div'), nil, nil),
|
275
|
+
s(:text, "\n"),
|
276
|
+
), tree.ast
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
@@ -281,6 +281,17 @@ module BetterHtml
|
|
281
281
|
assert_equal "erb statement not allowed here; did you mean '<%=' ?", errors.first.message
|
282
282
|
end
|
283
283
|
|
284
|
+
test "disallowed script types" do
|
285
|
+
errors = parse(<<-EOF).errors
|
286
|
+
<script type="text/bogus">
|
287
|
+
</script>
|
288
|
+
EOF
|
289
|
+
|
290
|
+
assert_equal 1, errors.size
|
291
|
+
assert_equal 'type="text/bogus"', errors.first.location.source
|
292
|
+
assert_equal "text/bogus is not a valid type, valid types are text/javascript, text/template, text/html", errors.first.message
|
293
|
+
end
|
294
|
+
|
284
295
|
test "statements not allowed in javascript template" do
|
285
296
|
errors = parse(<<-JS, template_language: :javascript).errors
|
286
297
|
<% if foo %>
|
@@ -46,7 +46,17 @@ module BetterHtml
|
|
46
46
|
EOF
|
47
47
|
|
48
48
|
assert_equal 1, errors.size
|
49
|
-
assert_equal 'script', errors.first.location.source
|
49
|
+
assert_equal '<script type="text/javascript">', errors.first.location.source
|
50
|
+
assert_equal "No script tags allowed nested in lodash templates", errors.first.message
|
51
|
+
end
|
52
|
+
|
53
|
+
test "script tag names are unescaped" do
|
54
|
+
errors = parse(<<-EOF).errors
|
55
|
+
<script type="text/javascript"></script>
|
56
|
+
EOF
|
57
|
+
|
58
|
+
assert_equal 1, errors.size
|
59
|
+
assert_equal '<script type="text/javascript">', errors.first.location.source
|
50
60
|
assert_equal "No script tags allowed nested in lodash templates", errors.first.message
|
51
61
|
end
|
52
62
|
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'better_html/tokenizer/html_erb'
|
3
|
+
|
4
|
+
module BetterHtml
|
5
|
+
module Tokenizer
|
6
|
+
class HtmlErbTest < ActiveSupport::TestCase
|
7
|
+
test "text" do
|
8
|
+
scanner = HtmlErb.new("just some text")
|
9
|
+
assert_equal 1, scanner.tokens.size
|
10
|
+
|
11
|
+
assert_attributes ({
|
12
|
+
type: :text,
|
13
|
+
loc: { start: 0, stop: 13, source: 'just some text' }
|
14
|
+
}), scanner.tokens[0]
|
15
|
+
end
|
16
|
+
|
17
|
+
test "statement" do
|
18
|
+
scanner = HtmlErb.new("<% statement %>")
|
19
|
+
assert_equal 3, scanner.tokens.size
|
20
|
+
|
21
|
+
assert_attributes ({ type: :erb_begin, loc: { start: 0, stop: 1, source: '<%' } }), scanner.tokens[0]
|
22
|
+
assert_attributes ({ type: :code, loc: { start: 2, stop: 12, source: ' statement ' } }), scanner.tokens[1]
|
23
|
+
assert_attributes ({ type: :erb_end, loc: { start: 13, stop: 14, source: '%>' } }), scanner.tokens[2]
|
24
|
+
end
|
25
|
+
|
26
|
+
test "debug statement" do
|
27
|
+
scanner = HtmlErb.new("<%# statement %>")
|
28
|
+
assert_equal 4, scanner.tokens.size
|
29
|
+
|
30
|
+
assert_attributes ({ type: :erb_begin, loc: { start: 0, stop: 1, source: '<%' } }), scanner.tokens[0]
|
31
|
+
assert_attributes ({ type: :indicator, loc: { start: 2, stop: 2, source: '#' } }), scanner.tokens[1]
|
32
|
+
assert_attributes ({ type: :code, loc: { start: 3, stop: 13, source: ' statement ' } }), scanner.tokens[2]
|
33
|
+
assert_attributes ({ type: :erb_end, loc: { start: 14, stop: 15, source: '%>' } }), scanner.tokens[3]
|
34
|
+
end
|
35
|
+
|
36
|
+
test "when multi byte characters are present in erb" do
|
37
|
+
code = "<% ui_helper 'your store’s' %>"
|
38
|
+
scanner = HtmlErb.new(code)
|
39
|
+
assert_equal 3, scanner.tokens.size
|
40
|
+
|
41
|
+
assert_attributes ({ type: :erb_begin, loc: { start: 0, stop: 1, source: '<%' } }), scanner.tokens[0]
|
42
|
+
assert_attributes ({ type: :code, loc: { start: 2, stop: 27, source: " ui_helper 'your store’s' " } }), scanner.tokens[1]
|
43
|
+
assert_attributes ({ type: :erb_end, loc: { start: 28, stop: 29, source: '%>' } }), scanner.tokens[2]
|
44
|
+
assert_equal code.length, scanner.current_position
|
45
|
+
end
|
46
|
+
|
47
|
+
test "when multi byte characters are present in text" do
|
48
|
+
code = "your store’s"
|
49
|
+
scanner = HtmlErb.new(code)
|
50
|
+
assert_equal 1, scanner.tokens.size
|
51
|
+
|
52
|
+
assert_attributes ({ type: :text, loc: { start: 0, stop: 11, source: 'your store’s' } }), scanner.tokens[0]
|
53
|
+
assert_equal code.length, scanner.current_position
|
54
|
+
end
|
55
|
+
|
56
|
+
test "when multi byte characters are present in html" do
|
57
|
+
code = "<div title='your store’s'>foo</div>"
|
58
|
+
scanner = HtmlErb.new(code)
|
59
|
+
assert_equal 14, scanner.tokens.size
|
60
|
+
|
61
|
+
assert_attributes ({ type: :tag_start, loc: { start: 0, stop: 0, source: '<' } }), scanner.tokens[0]
|
62
|
+
assert_attributes ({ type: :tag_name, loc: { start: 1, stop: 3, source: "div" } }), scanner.tokens[1]
|
63
|
+
assert_attributes ({ type: :whitespace, loc: { start: 4, stop: 4, source: " " } }), scanner.tokens[2]
|
64
|
+
assert_attributes ({ type: :attribute_name, loc: { start: 5, stop: 9, source: "title" } }), scanner.tokens[3]
|
65
|
+
assert_attributes ({ type: :equal, loc: { start: 10, stop: 10, source: "=" } }), scanner.tokens[4]
|
66
|
+
assert_attributes ({ type: :attribute_quoted_value_start, loc: { start: 11, stop: 11, source: "'" } }), scanner.tokens[5]
|
67
|
+
assert_attributes ({ type: :attribute_quoted_value, loc: { start: 12, stop: 23, source: "your store’s" } }), scanner.tokens[6]
|
68
|
+
assert_attributes ({ type: :attribute_quoted_value_end, loc: { start: 24, stop: 24, source: "'" } }), scanner.tokens[7]
|
69
|
+
assert_equal code.length, scanner.current_position
|
70
|
+
end
|
71
|
+
|
72
|
+
test "expression literal" do
|
73
|
+
scanner = HtmlErb.new("<%= literal %>")
|
74
|
+
assert_equal 4, scanner.tokens.size
|
75
|
+
|
76
|
+
assert_attributes ({ type: :erb_begin, loc: { start: 0, stop: 1, source: '<%' } }), scanner.tokens[0]
|
77
|
+
assert_attributes ({ type: :indicator, loc: { start: 2, stop: 2, source: '=' } }), scanner.tokens[1]
|
78
|
+
assert_attributes ({ type: :code, loc: { start: 3, stop: 11, source: ' literal ' } }), scanner.tokens[2]
|
79
|
+
assert_attributes ({ type: :erb_end, loc: { start: 12, stop: 13, source: '%>' } }), scanner.tokens[3]
|
80
|
+
end
|
81
|
+
|
82
|
+
test "expression escaped" do
|
83
|
+
scanner = HtmlErb.new("<%== escaped %>")
|
84
|
+
assert_equal 4, scanner.tokens.size
|
85
|
+
|
86
|
+
assert_attributes ({ type: :erb_begin, loc: { start: 0, stop: 1, source: '<%' } }), scanner.tokens[0]
|
87
|
+
assert_attributes ({ type: :indicator, loc: { start: 2, stop: 3, source: '==' } }), scanner.tokens[1]
|
88
|
+
assert_attributes ({ type: :code, loc: { start: 4, stop: 12, source: ' escaped ' } }), scanner.tokens[2]
|
89
|
+
assert_attributes ({ type: :erb_end, loc: { start: 13, stop: 14, source: '%>' } }), scanner.tokens[3]
|
90
|
+
end
|
91
|
+
|
92
|
+
test "line number for multi-line statements" do
|
93
|
+
scanner = HtmlErb.new("before <% multi\nline %> after")
|
94
|
+
assert_equal 5, scanner.tokens.size
|
95
|
+
|
96
|
+
assert_attributes ({ type: :text, loc: { line: 1, source: 'before ' } }), scanner.tokens[0]
|
97
|
+
assert_attributes ({ type: :erb_begin, loc: { line: 1, source: '<%' } }), scanner.tokens[1]
|
98
|
+
assert_attributes ({ type: :code, loc: { line: 1, source: " multi\nline " } }), scanner.tokens[2]
|
99
|
+
assert_attributes ({ type: :erb_end, loc: { line: 2, source: "%>" } }), scanner.tokens[3]
|
100
|
+
assert_attributes ({ type: :text, loc: { line: 2, source: " after" } }), scanner.tokens[4]
|
101
|
+
end
|
102
|
+
|
103
|
+
test "multi-line statements with trim" do
|
104
|
+
scanner = HtmlErb.new("before\n<% multi\nline -%>\nafter")
|
105
|
+
assert_equal 7, scanner.tokens.size
|
106
|
+
|
107
|
+
assert_attributes ({ type: :text, loc: { line: 1, source: "before\n" } }), scanner.tokens[0]
|
108
|
+
assert_attributes ({ type: :erb_begin, loc: { line: 2, source: '<%' } }), scanner.tokens[1]
|
109
|
+
assert_attributes ({ type: :code, loc: { line: 2, source: " multi\nline " } }), scanner.tokens[2]
|
110
|
+
assert_attributes ({ type: :trim, loc: { line: 3, source: "-" } }), scanner.tokens[3]
|
111
|
+
assert_attributes ({ type: :erb_end, loc: { line: 3, source: "%>" } }), scanner.tokens[4]
|
112
|
+
assert_attributes ({ type: :text, loc: { line: 3, source: "\n" } }), scanner.tokens[5]
|
113
|
+
assert_attributes ({ type: :text, loc: { line: 4, source: "after" } }), scanner.tokens[6]
|
114
|
+
end
|
115
|
+
|
116
|
+
test "multi-line expression with trim" do
|
117
|
+
scanner = HtmlErb.new("before\n<%= multi\nline -%>\nafter")
|
118
|
+
assert_equal 8, scanner.tokens.size
|
119
|
+
|
120
|
+
assert_attributes ({ type: :text, loc: { line: 1, source: "before\n" } }), scanner.tokens[0]
|
121
|
+
assert_attributes ({ type: :erb_begin, loc: { line: 2, source: '<%' } }), scanner.tokens[1]
|
122
|
+
assert_attributes ({ type: :indicator, loc: { line: 2, source: '=' } }), scanner.tokens[2]
|
123
|
+
assert_attributes ({ type: :code, loc: { line: 2, source: " multi\nline " } }), scanner.tokens[3]
|
124
|
+
assert_attributes ({ type: :trim, loc: { line: 3, source: "-" } }), scanner.tokens[4]
|
125
|
+
assert_attributes ({ type: :erb_end, loc: { line: 3, source: "%>" } }), scanner.tokens[5]
|
126
|
+
assert_attributes ({ type: :text, loc: { line: 3, source: "\n" } }), scanner.tokens[6]
|
127
|
+
assert_attributes ({ type: :text, loc: { line: 4, source: "after" } }), scanner.tokens[7]
|
128
|
+
end
|
129
|
+
|
130
|
+
test "line counts with comments" do
|
131
|
+
scanner = HtmlErb.new("before\n<%# BO$$ Mode %>\nafter")
|
132
|
+
assert_equal 7, scanner.tokens.size
|
133
|
+
|
134
|
+
assert_attributes ({ type: :text, loc: { line: 1, source: "before\n" } }), scanner.tokens[0]
|
135
|
+
assert_attributes ({ type: :erb_begin, loc: { line: 2, source: '<%' } }), scanner.tokens[1]
|
136
|
+
assert_attributes ({ type: :indicator, loc: { line: 2, source: '#' } }), scanner.tokens[2]
|
137
|
+
assert_attributes ({ type: :code, loc: { line: 2, source: " BO$$ Mode " } }), scanner.tokens[3]
|
138
|
+
assert_attributes ({ type: :erb_end, loc: { line: 2, source: "%>" } }), scanner.tokens[4]
|
139
|
+
assert_attributes ({ type: :text, loc: { line: 2, source: "\n" } }), scanner.tokens[5]
|
140
|
+
assert_attributes ({ type: :text, loc: { line: 3, source: "after" } }), scanner.tokens[6]
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def assert_attributes(attributes, token)
|
146
|
+
attributes.each do |key, value|
|
147
|
+
if value.nil?
|
148
|
+
assert_nil token.send(key)
|
149
|
+
elsif value.is_a?(Hash)
|
150
|
+
assert_attributes(value, token.send(key))
|
151
|
+
else
|
152
|
+
assert_equal value, token.send(key)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|