better_html 0.0.8 → 0.0.9
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/node_iterator/html_erb.rb +6 -3
- data/lib/better_html/node_iterator/html_lodash.rb +1 -1
- data/lib/better_html/node_iterator/javascript_erb.rb +8 -7
- data/lib/better_html/node_iterator/location.rb +38 -2
- data/lib/better_html/test_helper/ruby_expr.rb +89 -64
- data/lib/better_html/test_helper/safe_erb_tester.rb +134 -31
- data/lib/better_html/test_helper/safe_lodash_tester.rb +25 -12
- data/lib/better_html/test_helper/safety_error.rb +12 -0
- data/lib/better_html/version.rb +1 -1
- data/test/better_html/node_iterator/location_test.rb +36 -0
- data/test/better_html/test_helper/ruby_expr_test.rb +163 -86
- data/test/better_html/test_helper/safe_erb_tester_test.rb +63 -22
- data/test/better_html/test_helper/safe_lodash_tester_test.rb +5 -5
- metadata +19 -3
- data/lib/better_html/test_helper/safety_tester_base.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17ea4709e995bab97c314fd892b1926582788ab4
|
4
|
+
data.tar.gz: 497c12a9e1e060b4564d2310695fe06d54e3af86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6cb55f55293eb9875635480ada4148fd3afb4a148284e24998c5cfa0a0648598f5659a8dccc41ea2329aa630f194fa8fe7d7f08bebea732310e8aebc7f80f5ff
|
7
|
+
data.tar.gz: 8786bbc8fe5a6df5da740715413f6ef05e9b05302f4b9cf1fe5672f4eb98ef931519155e3f219b6b3dc53c5fd1142d626e7b8c5c6f008ca655f7e9eab8daba2c
|
@@ -14,6 +14,7 @@ module BetterHtml
|
|
14
14
|
def initialize(document)
|
15
15
|
@parser = HtmlTokenizer::Parser.new
|
16
16
|
@tokens = []
|
17
|
+
@document = document
|
17
18
|
super(document, regexp: REGEXP_WITHOUT_TRIM, trim: false)
|
18
19
|
end
|
19
20
|
|
@@ -29,7 +30,8 @@ module BetterHtml
|
|
29
30
|
type: :stmt,
|
30
31
|
code: code,
|
31
32
|
text: text,
|
32
|
-
location: Location.new(start, stop, @parser.line_number, @parser.column_number)
|
33
|
+
location: Location.new(@document, start, stop, @parser.line_number, @parser.column_number),
|
34
|
+
code_location: Location.new(@document, start+2, stop-2, @parser.line_number, @parser.column_number+2)
|
33
35
|
)
|
34
36
|
@parser.append_placeholder(text)
|
35
37
|
end
|
@@ -42,7 +44,8 @@ module BetterHtml
|
|
42
44
|
type: indicator == '=' ? :expr_literal : :expr_escaped,
|
43
45
|
code: code,
|
44
46
|
text: text,
|
45
|
-
location: Location.new(start, stop, @parser.line_number, @parser.column_number)
|
47
|
+
location: Location.new(@document, start, stop, @parser.line_number, @parser.column_number),
|
48
|
+
code_location: Location.new(@document, start+2+indicator.size, stop-2, @parser.line_number, @parser.column_number+2+indicator.size)
|
46
49
|
)
|
47
50
|
@parser.append_placeholder(text)
|
48
51
|
end
|
@@ -58,7 +61,7 @@ module BetterHtml
|
|
58
61
|
@tokens << Token.new(
|
59
62
|
type: type,
|
60
63
|
text: @parser.extract(start, stop),
|
61
|
-
location: Location.new(start, stop, line, column),
|
64
|
+
location: Location.new(@document, start, stop, line, column),
|
62
65
|
**(extra_attributes || {})
|
63
66
|
)
|
64
67
|
end
|
@@ -92,7 +92,7 @@ module BetterHtml
|
|
92
92
|
type: type,
|
93
93
|
text: text,
|
94
94
|
code: code,
|
95
|
-
location: Location.new(start, stop, line || @parser.line_number, column || @parser.column_number),
|
95
|
+
location: Location.new(@source, start, stop, line || @parser.line_number, column || @parser.column_number),
|
96
96
|
**(extra_attributes || {})
|
97
97
|
)
|
98
98
|
end
|
@@ -7,10 +7,11 @@ module BetterHtml
|
|
7
7
|
class JavascriptErb < ::Erubi::Engine
|
8
8
|
attr_reader :tokens
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@
|
10
|
+
def initialize(source)
|
11
|
+
@source = source
|
12
|
+
@parsed_document = ""
|
12
13
|
@tokens = []
|
13
|
-
super(
|
14
|
+
super(source, regexp: HtmlErb::REGEXP_WITHOUT_TRIM, trim: false)
|
14
15
|
end
|
15
16
|
|
16
17
|
def add_text(text)
|
@@ -33,21 +34,21 @@ module BetterHtml
|
|
33
34
|
private
|
34
35
|
|
35
36
|
def add_token(type, text, code = nil)
|
36
|
-
start = @
|
37
|
+
start = @parsed_document.size
|
37
38
|
stop = start + text.size
|
38
|
-
lines = @
|
39
|
+
lines = @parsed_document.split("\n", -1)
|
39
40
|
line = lines.empty? ? 1 : lines.size
|
40
41
|
column = lines.empty? ? 0 : lines.last.size
|
41
42
|
@tokens << Token.new(
|
42
43
|
type: type,
|
43
44
|
text: text,
|
44
45
|
code: code,
|
45
|
-
location: Location.new(start, stop, line, column)
|
46
|
+
location: Location.new(@source, start, stop, line, column)
|
46
47
|
)
|
47
48
|
end
|
48
49
|
|
49
50
|
def append(text)
|
50
|
-
@
|
51
|
+
@parsed_document << text
|
51
52
|
end
|
52
53
|
end
|
53
54
|
end
|
@@ -1,14 +1,50 @@
|
|
1
1
|
module BetterHtml
|
2
2
|
class NodeIterator
|
3
3
|
class Location
|
4
|
-
attr_accessor :start, :stop
|
4
|
+
attr_accessor :start, :stop
|
5
5
|
|
6
|
-
def initialize(start, stop, line, column)
|
6
|
+
def initialize(document, start, stop, line = nil, column = nil)
|
7
|
+
@document = document
|
7
8
|
@start = start
|
8
9
|
@stop = stop
|
9
10
|
@line = line
|
10
11
|
@column = column
|
11
12
|
end
|
13
|
+
|
14
|
+
def range
|
15
|
+
Range.new(start, stop-1)
|
16
|
+
end
|
17
|
+
|
18
|
+
def source
|
19
|
+
@document[range]
|
20
|
+
end
|
21
|
+
|
22
|
+
def line
|
23
|
+
@line ||= calculate_line
|
24
|
+
end
|
25
|
+
|
26
|
+
def column
|
27
|
+
@column ||= calculate_column
|
28
|
+
end
|
29
|
+
|
30
|
+
def line_source_with_underline
|
31
|
+
line_content = @document.lines[line-1]
|
32
|
+
line_content = line_content.nil? ? "" : line_content.gsub(/\n$/, '')
|
33
|
+
spaces = line_content.scan(/\A\s*/).first
|
34
|
+
column_without_spaces = column - spaces.length
|
35
|
+
underscore_length = [[stop - start, line_content.length - column_without_spaces].min, 1].max
|
36
|
+
"#{line_content.gsub(/\A\s*/, '')}\n#{' ' * column_without_spaces}#{'^' * underscore_length}"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def calculate_line
|
42
|
+
@document[0..start-1].scan("\n").count + 1
|
43
|
+
end
|
44
|
+
|
45
|
+
def calculate_column
|
46
|
+
@document[0..start-1]&.split("\n", -1)&.last&.length || 0
|
47
|
+
end
|
12
48
|
end
|
13
49
|
end
|
14
50
|
end
|
@@ -1,88 +1,113 @@
|
|
1
|
-
require '
|
2
|
-
require 'pp'
|
1
|
+
require 'parser/current'
|
3
2
|
|
4
3
|
module BetterHtml
|
5
4
|
module TestHelper
|
6
5
|
class RubyExpr
|
7
|
-
attr_reader :calls
|
8
|
-
|
9
6
|
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
10
7
|
|
11
8
|
class ParseError < RuntimeError; end
|
12
9
|
|
13
10
|
class MethodCall
|
14
11
|
attr_accessor :instance, :method, :arguments
|
15
|
-
end
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
raise ParseError, "cannot parse code" unless tree && tree.last.first.is_a?(Array)
|
22
|
-
@tree = tree.last.first
|
23
|
-
else
|
24
|
-
@tree = tree
|
13
|
+
def initialize(instance, method, arguments)
|
14
|
+
@instance = instance
|
15
|
+
@method = method
|
16
|
+
@arguments = arguments
|
25
17
|
end
|
26
|
-
|
27
|
-
|
18
|
+
|
19
|
+
def self.from_ast_node(node)
|
20
|
+
new(node.children[0], node.children[1], node.children[2..-1])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(ast)
|
25
|
+
raise ArgumentError, "expect first argument to be Parser::AST::Node" unless ast.is_a?(Parser::AST::Node)
|
26
|
+
@ast = ast
|
28
27
|
end
|
29
28
|
|
30
|
-
|
29
|
+
def self.parse(code)
|
30
|
+
parser = Parser::CurrentRuby.new
|
31
|
+
parser.diagnostics.consumer = lambda { |diag| }
|
32
|
+
buf = Parser::Source::Buffer.new('(string)')
|
33
|
+
buf.source = code.sub(BLOCK_EXPR, '')
|
34
|
+
parsed = parser.parse(buf)
|
35
|
+
raise ParseError, "error parsing code: #{code.inspect}" unless parsed
|
36
|
+
new(parsed)
|
37
|
+
end
|
38
|
+
|
39
|
+
def start
|
40
|
+
@ast.loc.expression.begin_pos
|
41
|
+
end
|
31
42
|
|
32
|
-
def
|
33
|
-
|
43
|
+
def end
|
44
|
+
@ast.loc.expression.end_pos
|
34
45
|
end
|
35
46
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
def traverse(current=@ast, only: nil, &block)
|
48
|
+
yield current if node_match?(current, only)
|
49
|
+
each_child_node(current) do |child|
|
50
|
+
traverse(child, only: only, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def each_child_node(current=@ast, only: nil, range: (0..-1))
|
55
|
+
current.children[range].each do |child|
|
56
|
+
if child.is_a?(Parser::AST::Node) && node_match?(child, only)
|
57
|
+
yield child
|
47
58
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def node_match?(current, type)
|
63
|
+
type.nil? || Array[type].flatten.include?(current.type)
|
64
|
+
end
|
65
|
+
|
66
|
+
STATIC_TYPES = [:str, :int, :true, :false, :nil]
|
67
|
+
|
68
|
+
def each_return_value_recursive(current=@ast, only: nil, &block)
|
69
|
+
case current.type
|
70
|
+
when :send, :csend, :ivar, *STATIC_TYPES
|
71
|
+
yield current if node_match?(current, only)
|
72
|
+
when :if, :masgn, :lvasgn
|
73
|
+
# first child is ignored as it does not contain return values
|
74
|
+
# for example, in `foo ? x : y` we only care about x and y, not foo
|
75
|
+
each_child_node(current, range: 1..-1) do |child|
|
76
|
+
each_return_value_recursive(child, only: only, &block)
|
51
77
|
end
|
52
|
-
when :assign, :massign
|
53
|
-
parse_expr(expr[2])
|
54
|
-
when :call
|
55
|
-
@calls << obj = MethodCall.new
|
56
|
-
obj.instance = expr[1]
|
57
|
-
obj.method = parse_expr(expr[3])
|
58
|
-
obj
|
59
|
-
when :fcall, :vcall
|
60
|
-
@calls << obj = MethodCall.new
|
61
|
-
obj.method = parse_expr(expr[1])
|
62
|
-
obj
|
63
|
-
when :method_add_arg
|
64
|
-
# foo(bar) -> foo=expr[1], bar=expr[2]
|
65
|
-
obj = parse_expr(expr[1])
|
66
|
-
obj.arguments = parse_expr(expr[2])
|
67
|
-
obj
|
68
|
-
when :command
|
69
|
-
# foo bar -> foo=expr[1], bar=expr[2]
|
70
|
-
@calls << obj = MethodCall.new
|
71
|
-
obj.method = parse_expr(expr[1])
|
72
|
-
obj.arguments = parse_expr(expr[2])
|
73
|
-
obj
|
74
|
-
when :arg_paren
|
75
|
-
parse_expr(expr[1]) unless expr[1].nil?
|
76
|
-
when :if_mod, :unless_mod
|
77
|
-
# foo if bar -> bar=expr[1], foo=expr[2]
|
78
|
-
parse_expr(expr[2])
|
79
|
-
when :ifop
|
80
|
-
# foo ? bar : baz -> foo=expr[1], bar=expr[2], baz=expr[3]
|
81
|
-
parse_expr(expr[2])
|
82
|
-
parse_expr(expr[3])
|
83
78
|
else
|
84
|
-
|
79
|
+
each_child_node(current) do |child|
|
80
|
+
each_return_value_recursive(child, only: only, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def static_value?
|
86
|
+
returns = []
|
87
|
+
each_return_value_recursive do |node|
|
88
|
+
returns << node
|
89
|
+
end
|
90
|
+
return false if returns.size == 0
|
91
|
+
returns.each do |node|
|
92
|
+
if STATIC_TYPES.include?(node.type)
|
93
|
+
next
|
94
|
+
elsif node.type == :dstr
|
95
|
+
each_child_node(node) do |child|
|
96
|
+
return false if child.type != :str
|
97
|
+
end
|
98
|
+
else
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
def calls
|
106
|
+
calls = []
|
107
|
+
each_return_value_recursive(only: [:send, :csend]) do |node|
|
108
|
+
calls << MethodCall.from_ast_node(node)
|
85
109
|
end
|
110
|
+
calls
|
86
111
|
end
|
87
112
|
end
|
88
113
|
end
|
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'better_html/test_helper/ruby_expr'
|
2
|
-
|
2
|
+
require 'better_html/test_helper/safety_error'
|
3
|
+
require 'parser/current'
|
3
4
|
|
4
5
|
module BetterHtml
|
5
6
|
module TestHelper
|
6
7
|
module SafeErbTester
|
7
|
-
include SafetyTesterBase
|
8
|
-
|
9
8
|
SAFETY_TIPS = <<-EOF
|
10
9
|
-----------
|
11
10
|
|
@@ -34,7 +33,11 @@ EOF
|
|
34
33
|
|
35
34
|
message = ""
|
36
35
|
tester.errors.each do |error|
|
37
|
-
message <<
|
36
|
+
message << <<~EOL
|
37
|
+
On line #{error.location.line}
|
38
|
+
#{error.message}
|
39
|
+
#{error.location.line_source_with_underline}\n
|
40
|
+
EOL
|
38
41
|
end
|
39
42
|
|
40
43
|
message << SAFETY_TIPS
|
@@ -58,8 +61,8 @@ EOF
|
|
58
61
|
validate!
|
59
62
|
end
|
60
63
|
|
61
|
-
def add_error(
|
62
|
-
@errors.add(
|
64
|
+
def add_error(message, location:)
|
65
|
+
@errors.add(SafetyError.new(message, location: location))
|
63
66
|
end
|
64
67
|
|
65
68
|
def validate!
|
@@ -80,6 +83,8 @@ EOF
|
|
80
83
|
validate_javascript_tag_type(node) unless node.closing?
|
81
84
|
end
|
82
85
|
when BetterHtml::NodeIterator::Text
|
86
|
+
validate_text_content(node)
|
87
|
+
|
83
88
|
if @nodes.template_language == :javascript
|
84
89
|
validate_script_tag_content(node)
|
85
90
|
validate_no_statements(node)
|
@@ -102,7 +107,10 @@ EOF
|
|
102
107
|
typeattr = element['type']
|
103
108
|
return if typeattr.nil?
|
104
109
|
if !VALID_JAVASCRIPT_TAG_TYPES.include?(typeattr.unescaped_value)
|
105
|
-
add_error(
|
110
|
+
add_error(
|
111
|
+
"#{typeattr.value} is not a valid type, valid types are #{VALID_JAVASCRIPT_TAG_TYPES.join(', ')}",
|
112
|
+
location: typeattr.value_parts.first.location
|
113
|
+
)
|
106
114
|
end
|
107
115
|
end
|
108
116
|
|
@@ -111,48 +119,132 @@ EOF
|
|
111
119
|
attr_token.value_parts.each do |value_token|
|
112
120
|
case value_token.type
|
113
121
|
when :expr_literal
|
114
|
-
|
122
|
+
begin
|
123
|
+
expr = RubyExpr.parse(value_token.code)
|
124
|
+
validate_tag_expression(value_token, expr, attr_token.name)
|
125
|
+
rescue RubyExpr::ParseError
|
126
|
+
nil
|
127
|
+
end
|
115
128
|
when :expr_escaped
|
116
|
-
add_error(
|
129
|
+
add_error(
|
130
|
+
"erb interpolation with '<%==' inside html attribute is never safe",
|
131
|
+
location: value_token.location
|
132
|
+
)
|
117
133
|
end
|
118
134
|
end
|
119
135
|
end
|
120
136
|
end
|
121
137
|
|
122
|
-
def
|
123
|
-
|
138
|
+
def validate_text_content(text)
|
139
|
+
text.content_parts.each do |text_token|
|
140
|
+
case text_token.type
|
141
|
+
when :stmt, :expr_literal, :expr_escaped
|
142
|
+
begin
|
143
|
+
expr = RubyExpr.parse(text_token.code)
|
144
|
+
validate_ruby_helper(text_token, expr)
|
145
|
+
rescue RubyExpr::ParseError
|
146
|
+
nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def validate_ruby_helper(parent_token, expr)
|
153
|
+
expr.traverse(only: [:send, :csend]) do |send_node|
|
154
|
+
expr.each_child_node(send_node, only: :hash) do |hash_node|
|
155
|
+
expr.each_child_node(hash_node, only: :pair) do |pair_node|
|
156
|
+
validate_ruby_helper_hash_entry(parent_token, expr, nil, *pair_node.children)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def validate_ruby_helper_hash_entry(parent_token, expr, key_prefix, key_node, value_node)
|
163
|
+
return unless [:sym, :str].include?(key_node.type)
|
164
|
+
key = [key_prefix, key_node.children.first.to_s].compact.join('-').dasherize
|
165
|
+
case value_node.type
|
166
|
+
when :dstr
|
167
|
+
validate_ruby_helper_hash_value(parent_token, expr, key, value_node)
|
168
|
+
when :hash
|
169
|
+
if key == 'data'
|
170
|
+
expr.each_child_node(value_node, only: :pair) do |pair_node|
|
171
|
+
validate_ruby_helper_hash_entry(parent_token, expr, key, *pair_node.children)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def validate_ruby_helper_hash_value(parent_token, expr, attr_name, hash_value)
|
178
|
+
expr.each_child_node(hash_value, only: :begin) do |child|
|
179
|
+
validate_tag_expression(parent_token, RubyExpr.new(child), attr_name)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def validate_tag_expression(parent_token, expr, attr_name)
|
184
|
+
return if expr.static_value?
|
124
185
|
|
125
186
|
if javascript_attribute_name?(attr_name) && expr.calls.empty?
|
126
|
-
add_error(
|
187
|
+
add_error(
|
188
|
+
"erb interpolation in javascript attribute must call '(...).to_json'",
|
189
|
+
location: NodeIterator::Location.new(
|
190
|
+
@data,
|
191
|
+
parent_token.code_location.start + expr.start,
|
192
|
+
parent_token.code_location.start + expr.end
|
193
|
+
)
|
194
|
+
)
|
127
195
|
return
|
128
196
|
end
|
129
197
|
|
130
198
|
expr.calls.each do |call|
|
131
|
-
if call.method ==
|
132
|
-
add_error(
|
133
|
-
|
134
|
-
|
199
|
+
if call.method == :raw
|
200
|
+
add_error(
|
201
|
+
"erb interpolation with '<%= raw(...) %>' inside html attribute is never safe",
|
202
|
+
location: NodeIterator::Location.new(
|
203
|
+
@data,
|
204
|
+
parent_token.code_location.start + expr.start,
|
205
|
+
parent_token.code_location.start + expr.end
|
206
|
+
)
|
207
|
+
)
|
208
|
+
elsif call.method == :html_safe
|
209
|
+
add_error(
|
210
|
+
"erb interpolation with '<%= (...).html_safe %>' inside html attribute is never safe",
|
211
|
+
location: NodeIterator::Location.new(
|
212
|
+
@data,
|
213
|
+
parent_token.code_location.start + expr.start,
|
214
|
+
parent_token.code_location.start + expr.end
|
215
|
+
)
|
216
|
+
)
|
135
217
|
elsif javascript_attribute_name?(attr_name) && !javascript_safe_method?(call.method)
|
136
|
-
add_error(
|
218
|
+
add_error(
|
219
|
+
"erb interpolation in javascript attribute must call '(...).to_json'",
|
220
|
+
location: NodeIterator::Location.new(
|
221
|
+
@data,
|
222
|
+
parent_token.code_location.start + expr.start,
|
223
|
+
parent_token.code_location.start + expr.end
|
224
|
+
)
|
225
|
+
)
|
137
226
|
end
|
138
227
|
end
|
139
228
|
end
|
140
229
|
|
141
230
|
def javascript_attribute_name?(name)
|
142
|
-
BetterHtml.config.javascript_attribute_names.any?{ |other| other === name }
|
231
|
+
BetterHtml.config.javascript_attribute_names.any?{ |other| other === name.to_s }
|
143
232
|
end
|
144
233
|
|
145
234
|
def javascript_safe_method?(name)
|
146
|
-
BetterHtml.config.javascript_safe_methods.include?(name)
|
235
|
+
BetterHtml.config.javascript_safe_methods.include?(name.to_s)
|
147
236
|
end
|
148
237
|
|
149
238
|
def validate_script_tag_content(node)
|
150
239
|
node.content_parts.each do |token|
|
151
240
|
case token.type
|
152
241
|
when :expr_literal, :expr_escaped
|
153
|
-
expr = RubyExpr.
|
242
|
+
expr = RubyExpr.parse(token.code)
|
154
243
|
if expr.calls.empty?
|
155
|
-
add_error(
|
244
|
+
add_error(
|
245
|
+
"erb interpolation in javascript tag must call '(...).to_json'",
|
246
|
+
location: token.location,
|
247
|
+
)
|
156
248
|
else
|
157
249
|
validate_script_expression(node, token, expr)
|
158
250
|
end
|
@@ -162,14 +254,19 @@ EOF
|
|
162
254
|
|
163
255
|
def validate_script_expression(node, token, expr)
|
164
256
|
expr.calls.each do |call|
|
165
|
-
if call.method ==
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
257
|
+
if call.method == :raw
|
258
|
+
call.arguments.each do |argument_node|
|
259
|
+
arguments_expr = RubyExpr.new(argument_node)
|
260
|
+
validate_script_expression(node, token, arguments_expr)
|
261
|
+
end
|
262
|
+
elsif call.method == :html_safe
|
263
|
+
instance_expr = RubyExpr.new(call.instance)
|
170
264
|
validate_script_expression(node, token, instance_expr)
|
171
265
|
elsif !javascript_safe_method?(call.method)
|
172
|
-
add_error(
|
266
|
+
add_error(
|
267
|
+
"erb interpolation in javascript tag must call '(...).to_json'",
|
268
|
+
location: token.location,
|
269
|
+
)
|
173
270
|
end
|
174
271
|
end
|
175
272
|
end
|
@@ -177,7 +274,10 @@ EOF
|
|
177
274
|
def validate_no_statements(node)
|
178
275
|
node.content_parts.each do |token|
|
179
276
|
if token.type == :stmt && !(/\A\s*end/m === token.code)
|
180
|
-
add_error(
|
277
|
+
add_error(
|
278
|
+
"erb statement not allowed here; did you mean '<%=' ?",
|
279
|
+
location: token.location,
|
280
|
+
)
|
181
281
|
end
|
182
282
|
end
|
183
283
|
end
|
@@ -186,12 +286,15 @@ EOF
|
|
186
286
|
node.content_parts.each do |token|
|
187
287
|
if [:stmt, :expr_literal, :expr_escaped].include?(token.type)
|
188
288
|
expr = begin
|
189
|
-
RubyExpr.
|
289
|
+
RubyExpr.parse(token.code)
|
190
290
|
rescue RubyExpr::ParseError
|
191
291
|
next
|
192
292
|
end
|
193
|
-
if expr.calls.size == 1 && expr.calls.first.method ==
|
194
|
-
add_error(
|
293
|
+
if expr.calls.size == 1 && expr.calls.first.method == :javascript_tag
|
294
|
+
add_error(
|
295
|
+
"'javascript_tag do' syntax is deprecated; use inline <script> instead",
|
296
|
+
location: token.location,
|
297
|
+
)
|
195
298
|
end
|
196
299
|
end
|
197
300
|
end
|