herb 0.9.7-arm-linux-gnu → 0.10.0-arm-linux-gnu
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/README.md +1 -0
- data/ext/herb/extconf.rb +1 -0
- data/ext/herb/extension.c +108 -0
- data/herb.gemspec +1 -1
- data/lib/herb/3.2/herb.so +0 -0
- data/lib/herb/3.3/herb.so +0 -0
- data/lib/herb/3.4/herb.so +0 -0
- data/lib/herb/4.0/herb.so +0 -0
- data/lib/herb/action_view/render_analyzer.rb +1057 -0
- data/lib/herb/ast/erb_render_node.rb +155 -0
- data/lib/herb/bootstrap.rb +0 -1
- data/lib/herb/cli.rb +253 -19
- data/lib/herb/colors.rb +18 -0
- data/lib/herb/configuration.rb +49 -13
- data/lib/herb/defaults.yml +3 -0
- data/lib/herb/dev/runner.rb +445 -0
- data/lib/herb/dev/server.rb +207 -0
- data/lib/herb/dev/server_entry.rb +128 -0
- data/lib/herb/diff_operation.rb +34 -0
- data/lib/herb/diff_result.rb +59 -0
- data/lib/herb/engine/compiler.rb +56 -3
- data/lib/herb/engine/validators/render_validator.rb +92 -0
- data/lib/herb/engine.rb +58 -4
- data/lib/herb/html/util.rb +16 -0
- data/lib/herb/project.rb +1 -6
- data/lib/herb/version.rb +1 -1
- data/lib/herb.rb +41 -5
- data/sig/herb/action_view/render_analyzer.rbs +122 -0
- data/sig/herb/ast/erb_render_node.rbs +29 -0
- data/sig/herb/colors.rbs +12 -0
- data/sig/herb/configuration.rbs +20 -1
- data/sig/herb/dev/runner.rbs +59 -0
- data/sig/herb/dev/server.rbs +50 -0
- data/sig/herb/dev/server_entry.rbs +51 -0
- data/sig/herb/diff_operation.rbs +34 -0
- data/sig/herb/diff_result.rbs +34 -0
- data/sig/herb/engine/compiler.rbs +6 -0
- data/sig/herb/engine/validators/render_validator.rbs +21 -0
- data/sig/herb/engine.rbs +15 -0
- data/sig/herb/html/util.rbs +13 -0
- data/sig/herb.rbs +12 -2
- data/sig/herb_c_extension.rbs +1 -1
- data/sig/vendor/did_you_mean.rbs +6 -0
- data/sig/vendor/parallel.rbs +4 -0
- data/src/analyze/action_view/attribute_extraction_helpers.c +3 -2
- data/src/diff/herb_diff.c +137 -0
- data/src/diff/herb_diff_attributes.c +207 -0
- data/src/diff/herb_diff_children.c +518 -0
- data/src/diff/herb_diff_helpers.c +114 -0
- data/src/diff/herb_diff_nodes.c +707 -0
- data/src/diff/herb_hash.c +42 -0
- data/src/diff/herb_hash_index_map.c +47 -0
- data/src/diff/herb_hash_map.c +104 -0
- data/src/diff/herb_hash_tree.c +680 -0
- data/src/include/diff/herb_diff.h +118 -0
- data/src/include/diff/herb_hash.h +25 -0
- data/src/include/diff/herb_hash_index_map.h +32 -0
- data/src/include/diff/herb_hash_map.h +30 -0
- data/src/include/herb.h +1 -0
- data/src/include/version.h +1 -1
- data/templates/javascript/packages/core/src/config.ts.erb +43 -0
- data/templates/rust/src/ast/nodes.rs.erb +1 -1
- data/templates/rust/src/config.rs.erb +50 -0
- data/templates/src/diff/herb_diff_helpers.c.erb +38 -0
- data/templates/src/diff/herb_diff_nodes.c.erb +224 -0
- data/templates/src/diff/herb_hash_tree.c.erb +147 -0
- data/templates/template.rb +4 -4
- metadata +40 -4
- data/lib/herb/3.0/herb.so +0 -0
- data/lib/herb/3.1/herb.so +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Herb
|
|
7
|
+
module Dev
|
|
8
|
+
class ServerEntry
|
|
9
|
+
SERVERS_DIR = File.expand_path("~/.herb/dev-servers").freeze
|
|
10
|
+
REQUIRED_KEYS = ["pid", "port", "project", "started_at"].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :pid, :port, :project, :started_at
|
|
13
|
+
|
|
14
|
+
def initialize(pid:, port:, project:, started_at: Time.now.utc.iso8601)
|
|
15
|
+
@pid = pid
|
|
16
|
+
@port = port
|
|
17
|
+
@project = project
|
|
18
|
+
@started_at = started_at
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def save
|
|
22
|
+
FileUtils.mkdir_p(SERVERS_DIR)
|
|
23
|
+
File.write(file_path, to_json)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def remove
|
|
27
|
+
File.delete(file_path)
|
|
28
|
+
rescue StandardError
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def alive?
|
|
33
|
+
self.class.process_alive?(pid)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_hash
|
|
37
|
+
{ pid: pid, port: port, project: project, started_at: started_at }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_json(*)
|
|
41
|
+
JSON.generate(to_hash)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def project_name
|
|
45
|
+
project&.split("/")&.last || "unknown"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def stop!
|
|
49
|
+
Process.kill("INT", pid)
|
|
50
|
+
remove
|
|
51
|
+
true
|
|
52
|
+
rescue Errno::ESRCH
|
|
53
|
+
remove
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
def all
|
|
59
|
+
FileUtils.mkdir_p(SERVERS_DIR)
|
|
60
|
+
|
|
61
|
+
Dir.glob(File.join(SERVERS_DIR, "*.json")).filter_map do |path|
|
|
62
|
+
entry = load_file(path)
|
|
63
|
+
|
|
64
|
+
if entry&.alive?
|
|
65
|
+
entry
|
|
66
|
+
else
|
|
67
|
+
begin
|
|
68
|
+
File.delete(path)
|
|
69
|
+
rescue StandardError
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def find_by_port(port)
|
|
78
|
+
all.find { |entry| entry.port == port }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def find_by_project(project_path)
|
|
82
|
+
all.find { |entry| entry.project == project_path }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def stop_all
|
|
86
|
+
all.each(&:stop!)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def process_alive?(pid)
|
|
90
|
+
return false unless pid
|
|
91
|
+
|
|
92
|
+
Process.kill(0, pid)
|
|
93
|
+
true
|
|
94
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
95
|
+
false
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def load_file(path)
|
|
101
|
+
data = JSON.parse(File.read(path))
|
|
102
|
+
|
|
103
|
+
return nil unless data.is_a?(Hash) && REQUIRED_KEYS.all? { |key| data.key?(key) }
|
|
104
|
+
|
|
105
|
+
new(
|
|
106
|
+
pid: data["pid"],
|
|
107
|
+
port: data["port"],
|
|
108
|
+
project: data["project"],
|
|
109
|
+
started_at: data["started_at"]
|
|
110
|
+
)
|
|
111
|
+
rescue JSON::ParserError, Errno::ENOENT, Errno::EACCES
|
|
112
|
+
begin
|
|
113
|
+
File.delete(path)
|
|
114
|
+
rescue StandardError
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def file_path
|
|
124
|
+
File.join(SERVERS_DIR, "#{pid}.json")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module Herb
|
|
5
|
+
DiffOperation = Data.define(
|
|
6
|
+
:type, #: Symbol
|
|
7
|
+
:path, #: Array[Integer]
|
|
8
|
+
:old_node, #: Herb::AST::Node?
|
|
9
|
+
:new_node, #: Herb::AST::Node?
|
|
10
|
+
:old_index, #: Integer
|
|
11
|
+
:new_index #: Integer
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
class DiffOperation
|
|
15
|
+
#: () -> Hash[Symbol, untyped]
|
|
16
|
+
def to_hash
|
|
17
|
+
{
|
|
18
|
+
type: type,
|
|
19
|
+
path: path,
|
|
20
|
+
old_node: old_node,
|
|
21
|
+
new_node: new_node,
|
|
22
|
+
old_index: old_index,
|
|
23
|
+
new_index: new_index,
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
alias to_h to_hash
|
|
28
|
+
|
|
29
|
+
#: () -> String
|
|
30
|
+
def inspect
|
|
31
|
+
"#<#{self.class.name} type=#{type} path=[#{path.join(", ")}]>"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module Herb
|
|
5
|
+
class DiffResult
|
|
6
|
+
include Enumerable #[Herb::DiffOperation]
|
|
7
|
+
|
|
8
|
+
attr_reader :operations #: Array[Herb::DiffOperation]
|
|
9
|
+
|
|
10
|
+
#: (bool, Array[Herb::DiffOperation]) -> void
|
|
11
|
+
def initialize(identical, operations)
|
|
12
|
+
@identical = identical
|
|
13
|
+
@operations = operations.freeze
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#: () { (Herb::DiffOperation) -> void } -> void
|
|
18
|
+
#: () -> Enumerator[Herb::DiffOperation, void]
|
|
19
|
+
def each(&block)
|
|
20
|
+
return operations.each unless block
|
|
21
|
+
|
|
22
|
+
operations.each(&block)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: () -> bool
|
|
26
|
+
def identical?
|
|
27
|
+
@identical
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#: () -> Integer
|
|
31
|
+
def operation_count
|
|
32
|
+
operations.size
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
#: () -> bool
|
|
36
|
+
def changed?
|
|
37
|
+
!identical?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#: () -> Hash[Symbol, untyped]
|
|
41
|
+
def to_hash
|
|
42
|
+
{
|
|
43
|
+
identical: identical?,
|
|
44
|
+
operations: operations.map(&:to_hash),
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
alias to_h to_hash
|
|
49
|
+
|
|
50
|
+
#: () -> String
|
|
51
|
+
def inspect
|
|
52
|
+
if identical?
|
|
53
|
+
"#<#{self.class.name} identical>"
|
|
54
|
+
else
|
|
55
|
+
"#<#{self.class.name} #{operation_count} operation#{"s" unless operation_count == 1}>"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/herb/engine/compiler.rb
CHANGED
|
@@ -25,6 +25,7 @@ module Herb
|
|
|
25
25
|
@last_trim_consumed_newline = false
|
|
26
26
|
@pending_leading_whitespace = nil
|
|
27
27
|
@pending_leading_whitespace_insert_index = 0
|
|
28
|
+
@current_element_source = nil
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def generate_output
|
|
@@ -60,7 +61,20 @@ module Herb
|
|
|
60
61
|
with_element_context(node) do
|
|
61
62
|
visit(node.open_tag)
|
|
62
63
|
visit_all(node.body)
|
|
63
|
-
|
|
64
|
+
|
|
65
|
+
tag_name = node.tag_name&.value&.downcase
|
|
66
|
+
|
|
67
|
+
if node.open_tag.is_a?(Herb::AST::ERBOpenTagNode) && tag_name && node.close_tag
|
|
68
|
+
if node.close_tag.is_a?(Herb::AST::ERBEndNode)
|
|
69
|
+
remove_trailing_whitespace_from_last_token! if left_trim?(node.close_tag)
|
|
70
|
+
add_text("</#{tag_name}>")
|
|
71
|
+
@trim_next_whitespace = true
|
|
72
|
+
else
|
|
73
|
+
add_text("</#{tag_name}>")
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
visit(node.close_tag)
|
|
77
|
+
end
|
|
64
78
|
end
|
|
65
79
|
end
|
|
66
80
|
|
|
@@ -88,7 +102,9 @@ module Herb
|
|
|
88
102
|
|
|
89
103
|
return unless node.value
|
|
90
104
|
|
|
91
|
-
|
|
105
|
+
has_equals = node.equals.value&.include?("=")
|
|
106
|
+
add_text(has_equals ? node.equals.value : "=")
|
|
107
|
+
|
|
92
108
|
visit(node.value)
|
|
93
109
|
end
|
|
94
110
|
|
|
@@ -106,6 +122,40 @@ module Herb
|
|
|
106
122
|
pop_context
|
|
107
123
|
end
|
|
108
124
|
|
|
125
|
+
def visit_erb_open_tag_node(node)
|
|
126
|
+
tag_name = node.tag_name&.value
|
|
127
|
+
|
|
128
|
+
if tag_name
|
|
129
|
+
is_void = Herb::HTML::Util.void_element?(tag_name)
|
|
130
|
+
uses_self_closing = is_void && @current_element_source != "ActionView::Helpers::TagHelper#tag"
|
|
131
|
+
|
|
132
|
+
add_text("<")
|
|
133
|
+
add_text(tag_name)
|
|
134
|
+
|
|
135
|
+
node.children.each do |child|
|
|
136
|
+
visit(child)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
add_text(uses_self_closing ? " />" : ">")
|
|
140
|
+
else
|
|
141
|
+
process_erb_tag(node)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def visit_html_virtual_close_tag_node(node)
|
|
146
|
+
tag_name = node.tag_name&.value
|
|
147
|
+
|
|
148
|
+
return unless tag_name
|
|
149
|
+
|
|
150
|
+
add_text("</")
|
|
151
|
+
add_text(tag_name)
|
|
152
|
+
add_text(">")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def visit_ruby_literal_node(node)
|
|
156
|
+
add_expression(node.content)
|
|
157
|
+
end
|
|
158
|
+
|
|
109
159
|
def visit_html_close_tag_node(node)
|
|
110
160
|
tag_name = node.tag_name&.value&.downcase
|
|
111
161
|
|
|
@@ -165,7 +215,7 @@ module Herb
|
|
|
165
215
|
process_erb_tag(node)
|
|
166
216
|
end
|
|
167
217
|
|
|
168
|
-
def visit_erb_control_node(node, &
|
|
218
|
+
def visit_erb_control_node(node, &)
|
|
169
219
|
if node.content
|
|
170
220
|
apply_trim(node, node.content.value.strip)
|
|
171
221
|
end
|
|
@@ -332,6 +382,8 @@ module Herb
|
|
|
332
382
|
#: (untyped node) { () -> untyped } -> untyped
|
|
333
383
|
def with_element_context(node)
|
|
334
384
|
tag_name = node.tag_name&.value&.downcase
|
|
385
|
+
previous_element_source = @current_element_source
|
|
386
|
+
@current_element_source = node.element_source
|
|
335
387
|
|
|
336
388
|
@element_stack.push(tag_name) if tag_name
|
|
337
389
|
|
|
@@ -346,6 +398,7 @@ module Herb
|
|
|
346
398
|
pop_context if ["script", "style"].include?(tag_name)
|
|
347
399
|
|
|
348
400
|
@element_stack.pop if tag_name
|
|
401
|
+
@current_element_source = previous_element_source
|
|
349
402
|
end
|
|
350
403
|
|
|
351
404
|
def process_erb_tag(node, skip_comment_check: false)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../validator"
|
|
4
|
+
|
|
5
|
+
module Herb
|
|
6
|
+
class Engine
|
|
7
|
+
module Validators
|
|
8
|
+
class RenderValidator < Validator
|
|
9
|
+
def initialize(enabled: true, filename: nil, project_path: nil)
|
|
10
|
+
super(enabled: enabled)
|
|
11
|
+
|
|
12
|
+
@filename = filename
|
|
13
|
+
@project_path = project_path
|
|
14
|
+
@view_root = find_view_root
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def visit_erb_render_node(node)
|
|
18
|
+
if node.dynamic?
|
|
19
|
+
warning(
|
|
20
|
+
"Dynamic render call cannot be statically resolved",
|
|
21
|
+
node.location,
|
|
22
|
+
code: "RenderDynamic",
|
|
23
|
+
source: "RenderValidator"
|
|
24
|
+
)
|
|
25
|
+
elsif node.static_partial?
|
|
26
|
+
validate_partial_exists(node)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def validate_partial_exists(node)
|
|
35
|
+
return unless @filename
|
|
36
|
+
|
|
37
|
+
source_directory = @project_path.join(@filename).dirname
|
|
38
|
+
resolved = node.resolve(view_root: @view_root, source_directory: source_directory)
|
|
39
|
+
|
|
40
|
+
return if resolved
|
|
41
|
+
|
|
42
|
+
message = "Partial '#{node.partial_path}' could not be resolved."
|
|
43
|
+
searched = node.candidate_paths(nil, @view_root, source_directory)
|
|
44
|
+
|
|
45
|
+
if searched.any?
|
|
46
|
+
relative_paths = searched.map { |path| relative_to_project(path) }.uniq
|
|
47
|
+
message += "\n Looked in:\n"
|
|
48
|
+
|
|
49
|
+
relative_paths.each do |path|
|
|
50
|
+
message += " - #{path}\n"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
suggestions = node.similar_partials(view_root: @view_root, source_directory: source_directory)
|
|
55
|
+
|
|
56
|
+
if suggestions.any?
|
|
57
|
+
partial_suggestions, hint_suggestions = suggestions.partition { |suggestion| !suggestion.include?("exists as a template") }
|
|
58
|
+
|
|
59
|
+
if partial_suggestions.any?
|
|
60
|
+
message += " Did you mean: #{partial_suggestions.map { |suggestion| "'#{suggestion}'" }.join(", ")}?\n"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
hint_suggestions.each do |hint|
|
|
64
|
+
message += "\n Note: #{hint}\n"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
error(
|
|
69
|
+
message,
|
|
70
|
+
node.location,
|
|
71
|
+
code: "RenderUnresolved",
|
|
72
|
+
source: "RenderValidator"
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def find_view_root
|
|
77
|
+
return nil unless @project_path
|
|
78
|
+
|
|
79
|
+
view_root = @project_path.join("app", "views")
|
|
80
|
+
|
|
81
|
+
view_root.directory? ? view_root : nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def relative_to_project(path)
|
|
85
|
+
path.relative_path_from(@project_path).to_s
|
|
86
|
+
rescue ArgumentError
|
|
87
|
+
path.to_s
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/herb/engine.rb
CHANGED
|
@@ -14,12 +14,23 @@ require_relative "engine/validation_error_overlay"
|
|
|
14
14
|
require_relative "engine/validators/security_validator"
|
|
15
15
|
require_relative "engine/validators/nesting_validator"
|
|
16
16
|
require_relative "engine/validators/accessibility_validator"
|
|
17
|
+
require_relative "engine/validators/render_validator"
|
|
17
18
|
|
|
18
19
|
module Herb
|
|
19
20
|
class Engine
|
|
20
21
|
attr_reader :src, :filename, :project_path, :relative_file_path, :bufvar, :debug, :content_for_head,
|
|
21
22
|
:validation_error_template, :visitors, :enabled_validators
|
|
22
23
|
|
|
24
|
+
# @rbs!
|
|
25
|
+
# def self.optimize_warning_issued: () -> bool
|
|
26
|
+
# def self.optimize_warning_issued=: (bool) -> bool
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
attr_accessor :optimize_warning_issued #: bool
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
self.optimize_warning_issued = false
|
|
33
|
+
|
|
23
34
|
ESCAPE_TABLE = {
|
|
24
35
|
"&" => "&",
|
|
25
36
|
"<" => "<",
|
|
@@ -64,12 +75,20 @@ module Herb
|
|
|
64
75
|
@src = properties[:src] || String.new
|
|
65
76
|
@chain_appends = properties[:chain_appends]
|
|
66
77
|
@buffer_on_stack = false
|
|
67
|
-
@debug = properties.fetch(:debug, false)
|
|
78
|
+
@debug = properties.fetch(:debug, Herb.configuration.engine_option("debug", false))
|
|
68
79
|
@content_for_head = properties[:content_for_head]
|
|
69
80
|
@validation_error_template = nil
|
|
70
81
|
@validation_mode = properties.fetch(:validation_mode, :raise)
|
|
71
82
|
@enabled_validators = Herb.configuration.enabled_validators(properties[:validators] || {})
|
|
72
|
-
@
|
|
83
|
+
@optimize = properties.fetch(:optimize, Herb.configuration.engine_option("optimize", false))
|
|
84
|
+
@parser_options = properties.fetch(:parser_options, default_parser_options).transform_keys(&:to_sym)
|
|
85
|
+
|
|
86
|
+
if @optimize && !self.class.optimize_warning_issued
|
|
87
|
+
self.class.optimize_warning_issued = true
|
|
88
|
+
|
|
89
|
+
warn "[Herb] Compile-time optimizations are experimental. Output may differ from standard ActionView rendering."
|
|
90
|
+
end
|
|
91
|
+
|
|
73
92
|
@visitors = properties.fetch(:visitors, default_visitors)
|
|
74
93
|
|
|
75
94
|
if @debug && @visitors.empty?
|
|
@@ -110,7 +129,11 @@ module Herb
|
|
|
110
129
|
@src << "__herb = ::Herb::Engine; " if @escape && @escapefunc == "__herb.h"
|
|
111
130
|
@src << preamble
|
|
112
131
|
|
|
113
|
-
|
|
132
|
+
action_view_helpers = @optimize && source_may_contain_action_view_helpers?(input)
|
|
133
|
+
transform_conditionals = @optimize && action_view_helpers
|
|
134
|
+
parse_result = ::Herb.parse(input, **@parser_options, track_whitespace: true,
|
|
135
|
+
action_view_helpers: action_view_helpers,
|
|
136
|
+
transform_conditionals: transform_conditionals)
|
|
114
137
|
ast = parse_result.value
|
|
115
138
|
parser_errors = parse_result.errors
|
|
116
139
|
|
|
@@ -197,6 +220,10 @@ module Herb
|
|
|
197
220
|
end
|
|
198
221
|
end
|
|
199
222
|
|
|
223
|
+
def self.nested_attribute_value(value)
|
|
224
|
+
value.is_a?(::String) || value.is_a?(::Symbol) ? value.to_s : value.to_json
|
|
225
|
+
end
|
|
226
|
+
|
|
200
227
|
def self.comment?(code)
|
|
201
228
|
code.include?("#")
|
|
202
229
|
end
|
|
@@ -205,6 +232,26 @@ module Herb
|
|
|
205
232
|
code.match?(/<<[~-]?\s*['"`]?\w/)
|
|
206
233
|
end
|
|
207
234
|
|
|
235
|
+
def source_may_contain_action_view_helpers?(source)
|
|
236
|
+
self.class.action_view_helper_pattern.match?(source)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def self.action_view_helper_pattern
|
|
240
|
+
@action_view_helper_pattern ||= begin
|
|
241
|
+
require_relative "action_view/helper_registry"
|
|
242
|
+
|
|
243
|
+
names = ::Herb::ActionView::HelperRegistry.supported.flat_map { |entry|
|
|
244
|
+
if entry.receiver_call_detect?
|
|
245
|
+
"#{entry.name}."
|
|
246
|
+
else
|
|
247
|
+
[entry.name, *entry.aliases]
|
|
248
|
+
end
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Regexp.new("\\b(?:#{names.map { |name| Regexp.escape(name) }.join("|")})")
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
208
255
|
protected
|
|
209
256
|
|
|
210
257
|
def add_text(text)
|
|
@@ -337,7 +384,7 @@ module Herb
|
|
|
337
384
|
@src << postamble
|
|
338
385
|
end
|
|
339
386
|
|
|
340
|
-
def with_buffer(&
|
|
387
|
+
def with_buffer(&)
|
|
341
388
|
if @chain_appends
|
|
342
389
|
@src << "; " << @bufvar unless @buffer_on_stack
|
|
343
390
|
yield
|
|
@@ -472,6 +519,13 @@ module Herb
|
|
|
472
519
|
[]
|
|
473
520
|
end
|
|
474
521
|
|
|
522
|
+
#: () -> Hash[Symbol, untyped]
|
|
523
|
+
def default_parser_options
|
|
524
|
+
fallback = {} #: Hash[Symbol, untyped]
|
|
525
|
+
|
|
526
|
+
Herb.configuration.engine_option("parser_options", fallback)
|
|
527
|
+
end
|
|
528
|
+
|
|
475
529
|
def ensure_valid_ruby!(source)
|
|
476
530
|
RubyVM::InstructionSequence.compile(source)
|
|
477
531
|
rescue SyntaxError => e
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Herb
|
|
5
|
+
module HTML
|
|
6
|
+
module Util
|
|
7
|
+
# TODO: extract to shared utility for all languages in .yml
|
|
8
|
+
VOID_ELEMENTS = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"].freeze #: Array[String]
|
|
9
|
+
|
|
10
|
+
#: (String) -> bool
|
|
11
|
+
def self.void_element?(tag_name)
|
|
12
|
+
VOID_ELEMENTS.include?(tag_name.downcase)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/herb/project.rb
CHANGED
|
@@ -903,12 +903,7 @@ module Herb
|
|
|
903
903
|
def ensure_parallel!
|
|
904
904
|
return if defined?(Parallel)
|
|
905
905
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
gemfile(true, quiet: true) do
|
|
909
|
-
source "https://rubygems.org"
|
|
910
|
-
gem "parallel"
|
|
911
|
-
end
|
|
906
|
+
Herb.ensure_installed { gem "parallel" } # steep:ignore
|
|
912
907
|
end
|
|
913
908
|
|
|
914
909
|
def separator
|
data/lib/herb/version.rb
CHANGED
data/lib/herb.rb
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# typed: false
|
|
3
3
|
|
|
4
|
+
module Herb
|
|
5
|
+
PARTIAL_EXTENSIONS = [
|
|
6
|
+
".html.erb", ".html.herb", ".erb", ".herb", ".turbo_stream.erb", ".turbo_stream.herb"
|
|
7
|
+
].freeze
|
|
8
|
+
|
|
9
|
+
PARTIAL_GLOB_PATTERN = "_*.{html.erb,html.herb,erb,herb,turbo_stream.erb,turbo_stream.herb}"
|
|
10
|
+
end
|
|
11
|
+
|
|
4
12
|
require_relative "herb/colors"
|
|
5
13
|
require_relative "herb/range"
|
|
6
14
|
require_relative "herb/position"
|
|
@@ -13,12 +21,15 @@ require_relative "herb/result"
|
|
|
13
21
|
require_relative "herb/lex_result"
|
|
14
22
|
require_relative "herb/parser_options"
|
|
15
23
|
require_relative "herb/parse_result"
|
|
24
|
+
require_relative "herb/diff_operation"
|
|
25
|
+
require_relative "herb/diff_result"
|
|
16
26
|
|
|
17
27
|
require_relative "herb/ast"
|
|
18
28
|
require_relative "herb/ast/node"
|
|
19
29
|
require_relative "herb/ast/nodes"
|
|
20
30
|
require_relative "herb/ast/erb_content_node"
|
|
21
31
|
require_relative "herb/ast/helpers"
|
|
32
|
+
require_relative "herb/ast/erb_render_node"
|
|
22
33
|
|
|
23
34
|
require_relative "herb/errors"
|
|
24
35
|
require_relative "herb/warnings"
|
|
@@ -29,6 +40,7 @@ require_relative "herb/configuration"
|
|
|
29
40
|
|
|
30
41
|
require_relative "herb/version"
|
|
31
42
|
|
|
43
|
+
require_relative "herb/html/util"
|
|
32
44
|
require_relative "herb/visitor"
|
|
33
45
|
require_relative "herb/engine"
|
|
34
46
|
|
|
@@ -69,13 +81,13 @@ end
|
|
|
69
81
|
module Herb
|
|
70
82
|
class << self
|
|
71
83
|
#: (String path, ?arena_stats: bool) -> LexResult
|
|
72
|
-
def lex_file(path, **
|
|
73
|
-
lex(File.read(path), **
|
|
84
|
+
def lex_file(path, **)
|
|
85
|
+
lex(File.read(path), **)
|
|
74
86
|
end
|
|
75
87
|
|
|
76
|
-
#: (String path, ?track_whitespace: bool, ?analyze: bool, ?strict: bool, ?arena_stats: bool) -> ParseResult
|
|
77
|
-
def parse_file(path, **
|
|
78
|
-
parse(File.read(path), **
|
|
88
|
+
#: (String path, ?track_whitespace: bool, ?analyze: bool, ?strict: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?arena_stats: bool) -> ParseResult
|
|
89
|
+
def parse_file(path, **)
|
|
90
|
+
parse(File.read(path), **)
|
|
79
91
|
end
|
|
80
92
|
|
|
81
93
|
#: (String source) -> Prism::ParseResult
|
|
@@ -96,5 +108,29 @@ module Herb
|
|
|
96
108
|
def reset_configuration!
|
|
97
109
|
@configuration = nil
|
|
98
110
|
end
|
|
111
|
+
|
|
112
|
+
def dev_server_port(project_path = nil)
|
|
113
|
+
require_relative "herb/dev/server_entry"
|
|
114
|
+
|
|
115
|
+
project_path ||= Dir.pwd
|
|
116
|
+
entry = Dev::ServerEntry.find_by_project(project_path)
|
|
117
|
+
entry&.port
|
|
118
|
+
rescue StandardError
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def ensure_installed(&block)
|
|
123
|
+
require "bundler/inline"
|
|
124
|
+
|
|
125
|
+
verbose = $VERBOSE
|
|
126
|
+
$VERBOSE = nil
|
|
127
|
+
|
|
128
|
+
gemfile(true, quiet: true) do # steep:ignore
|
|
129
|
+
source "https://rubygems.org" # steep:ignore
|
|
130
|
+
instance_eval(&block) # steep:ignore
|
|
131
|
+
end
|
|
132
|
+
ensure
|
|
133
|
+
$VERBOSE = verbose
|
|
134
|
+
end
|
|
99
135
|
end
|
|
100
136
|
end
|