better_html 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|