herb 0.8.10-arm-linux-gnu → 0.9.1-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/Makefile +11 -3
- data/README.md +64 -34
- data/Rakefile +48 -40
- data/config.yml +473 -34
- data/ext/herb/error_helpers.c +535 -140
- data/ext/herb/error_helpers.h +1 -0
- data/ext/herb/extconf.rb +67 -28
- data/ext/herb/extension.c +321 -51
- data/ext/herb/extension.h +1 -0
- data/ext/herb/extension_helpers.c +24 -14
- data/ext/herb/extension_helpers.h +2 -2
- data/ext/herb/nodes.c +647 -270
- data/ext/herb/nodes.h +1 -0
- data/herb.gemspec +3 -2
- data/lib/herb/3.0/herb.so +0 -0
- data/lib/herb/3.1/herb.so +0 -0
- 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/ast/helpers.rb +3 -3
- data/lib/herb/ast/node.rb +15 -2
- data/lib/herb/ast/nodes.rb +1530 -179
- data/lib/herb/bootstrap.rb +87 -0
- data/lib/herb/cli.rb +341 -31
- data/lib/herb/configuration.rb +248 -0
- data/lib/herb/defaults.yml +32 -0
- data/lib/herb/engine/compiler.rb +78 -11
- data/lib/herb/engine/debug_visitor.rb +13 -3
- data/lib/herb/engine/error_formatter.rb +13 -9
- data/lib/herb/engine/parser_error_overlay.rb +10 -6
- data/lib/herb/engine/validator.rb +8 -3
- data/lib/herb/engine/validators/nesting_validator.rb +2 -2
- data/lib/herb/engine.rb +119 -43
- data/lib/herb/errors.rb +808 -88
- data/lib/herb/lex_result.rb +1 -0
- data/lib/herb/location.rb +7 -3
- data/lib/herb/parse_result.rb +12 -2
- data/lib/herb/parser_options.rb +62 -0
- data/lib/herb/position.rb +1 -0
- data/lib/herb/prism_inspect.rb +120 -0
- data/lib/herb/project.rb +923 -331
- data/lib/herb/range.rb +1 -0
- data/lib/herb/token.rb +7 -1
- data/lib/herb/version.rb +1 -1
- data/lib/herb/visitor.rb +47 -2
- data/lib/herb/warnings.rb +6 -1
- data/lib/herb.rb +35 -3
- data/sig/herb/ast/helpers.rbs +2 -2
- data/sig/herb/ast/node.rbs +12 -2
- data/sig/herb/ast/nodes.rbs +773 -128
- data/sig/herb/bootstrap.rbs +31 -0
- data/sig/herb/configuration.rbs +89 -0
- data/sig/herb/engine/compiler.rbs +9 -1
- data/sig/herb/engine/debug_visitor.rbs +2 -0
- data/sig/herb/engine/validator.rbs +5 -1
- data/sig/herb/engine.rbs +21 -3
- data/sig/herb/errors.rbs +372 -63
- data/sig/herb/location.rbs +4 -0
- data/sig/herb/parse_result.rbs +4 -2
- data/sig/herb/parser_options.rbs +46 -0
- data/sig/herb/position.rbs +1 -0
- data/sig/herb/prism_inspect.rbs +28 -0
- data/sig/herb/range.rbs +1 -0
- data/sig/herb/token.rbs +6 -0
- data/sig/herb/visitor.rbs +31 -4
- data/sig/herb/warnings.rbs +6 -1
- data/sig/herb.rbs +14 -0
- data/sig/herb_c_extension.rbs +5 -2
- data/sig/rubyvm.rbs +5 -0
- data/sig/serialized_ast_errors.rbs +82 -6
- data/sig/serialized_ast_nodes.rbs +91 -6
- data/src/analyze/action_view/attribute_extraction_helpers.c +303 -0
- data/src/analyze/action_view/content_tag.c +78 -0
- data/src/analyze/action_view/link_to.c +167 -0
- data/src/analyze/action_view/registry.c +83 -0
- data/src/analyze/action_view/tag.c +70 -0
- data/src/analyze/action_view/tag_helper_node_builders.c +305 -0
- data/src/analyze/action_view/tag_helpers.c +815 -0
- data/src/analyze/action_view/turbo_frame_tag.c +88 -0
- data/src/analyze/analyze.c +885 -0
- data/src/{analyzed_ruby.c → analyze/analyzed_ruby.c} +13 -11
- data/src/analyze/builders.c +343 -0
- data/src/analyze/conditional_elements.c +594 -0
- data/src/analyze/conditional_open_tags.c +640 -0
- data/src/analyze/control_type.c +250 -0
- data/src/{analyze_helpers.c → analyze/helpers.c} +48 -23
- data/src/analyze/invalid_structures.c +193 -0
- data/src/{analyze_missing_end.c → analyze/missing_end.c} +33 -22
- data/src/analyze/parse_errors.c +84 -0
- data/src/analyze/prism_annotate.c +399 -0
- data/src/analyze/render_nodes.c +761 -0
- data/src/{analyze_transform.c → analyze/transform.c} +24 -3
- data/src/ast_node.c +17 -7
- data/src/ast_nodes.c +759 -387
- data/src/ast_pretty_print.c +264 -6
- data/src/errors.c +1454 -519
- data/src/extract.c +145 -49
- data/src/herb.c +52 -34
- data/src/html_util.c +241 -12
- data/src/include/analyze/action_view/attribute_extraction_helpers.h +36 -0
- data/src/include/analyze/action_view/tag_helper_handler.h +43 -0
- data/src/include/analyze/action_view/tag_helper_node_builders.h +70 -0
- data/src/include/analyze/action_view/tag_helpers.h +38 -0
- data/src/include/{analyze.h → analyze/analyze.h} +14 -4
- data/src/include/{analyzed_ruby.h → analyze/analyzed_ruby.h} +3 -3
- data/src/include/analyze/builders.h +27 -0
- data/src/include/analyze/conditional_elements.h +9 -0
- data/src/include/analyze/conditional_open_tags.h +9 -0
- data/src/include/analyze/control_type.h +14 -0
- data/src/include/{analyze_helpers.h → analyze/helpers.h} +4 -2
- data/src/include/analyze/invalid_structures.h +11 -0
- data/src/include/analyze/prism_annotate.h +16 -0
- data/src/include/analyze/render_nodes.h +11 -0
- data/src/include/ast_node.h +11 -5
- data/src/include/ast_nodes.h +154 -38
- data/src/include/ast_pretty_print.h +5 -0
- data/src/include/element_source.h +3 -8
- data/src/include/errors.h +206 -55
- data/src/include/extract.h +21 -5
- data/src/include/herb.h +18 -6
- data/src/include/herb_prism_node.h +13 -0
- data/src/include/html_util.h +7 -2
- data/src/include/io.h +3 -1
- data/src/include/lex_helpers.h +29 -0
- data/src/include/lexer.h +1 -1
- data/src/include/lexer_peek_helpers.h +87 -13
- data/src/include/lexer_struct.h +2 -0
- data/src/include/location.h +2 -1
- data/src/include/parser.h +28 -2
- data/src/include/parser_helpers.h +19 -3
- data/src/include/pretty_print.h +10 -5
- data/src/include/prism_context.h +45 -0
- data/src/include/prism_helpers.h +10 -7
- data/src/include/prism_serialized.h +12 -0
- data/src/include/token.h +16 -4
- data/src/include/token_struct.h +10 -3
- data/src/include/utf8.h +2 -1
- data/src/include/util/hb_allocator.h +78 -0
- data/src/include/util/hb_arena.h +6 -1
- data/src/include/util/hb_arena_debug.h +12 -1
- data/src/include/util/hb_array.h +7 -3
- data/src/include/util/hb_buffer.h +6 -4
- data/src/include/util/hb_foreach.h +79 -0
- data/src/include/util/hb_narray.h +8 -4
- data/src/include/util/hb_string.h +56 -9
- data/src/include/util.h +6 -3
- data/src/include/version.h +1 -1
- data/src/io.c +3 -2
- data/src/lexer.c +42 -30
- data/src/lexer_peek_helpers.c +12 -74
- data/src/location.c +2 -2
- data/src/main.c +53 -28
- data/src/parser.c +784 -247
- data/src/parser_helpers.c +110 -23
- data/src/parser_match_tags.c +129 -48
- data/src/pretty_print.c +29 -24
- data/src/prism_helpers.c +30 -27
- data/src/ruby_parser.c +2 -0
- data/src/token.c +151 -66
- data/src/token_matchers.c +0 -1
- data/src/utf8.c +7 -6
- data/src/util/hb_allocator.c +341 -0
- data/src/util/hb_arena.c +81 -56
- data/src/util/hb_arena_debug.c +32 -17
- data/src/util/hb_array.c +30 -15
- data/src/util/hb_buffer.c +17 -21
- data/src/util/hb_narray.c +22 -7
- data/src/util/hb_string.c +49 -35
- data/src/util.c +21 -11
- data/src/visitor.c +67 -0
- data/templates/ext/herb/error_helpers.c.erb +24 -11
- data/templates/ext/herb/error_helpers.h.erb +1 -0
- data/templates/ext/herb/nodes.c.erb +50 -16
- data/templates/ext/herb/nodes.h.erb +1 -0
- data/templates/java/error_helpers.c.erb +1 -1
- data/templates/java/nodes.c.erb +30 -8
- data/templates/java/org/herb/ast/Errors.java.erb +24 -1
- data/templates/java/org/herb/ast/Nodes.java.erb +80 -21
- data/templates/javascript/packages/core/src/errors.ts.erb +16 -3
- data/templates/javascript/packages/core/src/node-type-guards.ts.erb +3 -1
- data/templates/javascript/packages/core/src/nodes.ts.erb +109 -32
- data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +13 -4
- data/templates/javascript/packages/node/extension/nodes.cpp.erb +43 -4
- data/templates/lib/herb/ast/nodes.rb.erb +95 -32
- data/templates/lib/herb/errors.rb.erb +15 -3
- data/templates/lib/herb/visitor.rb.erb +2 -2
- data/templates/rust/src/ast/nodes.rs.erb +97 -44
- data/templates/rust/src/errors.rs.erb +2 -1
- data/templates/rust/src/nodes.rs.erb +168 -16
- data/templates/rust/src/union_types.rs.erb +60 -0
- data/templates/rust/src/visitor.rs.erb +81 -0
- data/templates/src/{analyze_missing_end.c.erb → analyze/missing_end.c.erb} +9 -6
- data/templates/src/{analyze_transform.c.erb → analyze/transform.c.erb} +2 -2
- data/templates/src/ast_nodes.c.erb +34 -26
- data/templates/src/ast_pretty_print.c.erb +24 -5
- data/templates/src/errors.c.erb +60 -54
- data/templates/src/include/ast_nodes.h.erb +6 -2
- data/templates/src/include/ast_pretty_print.h.erb +5 -0
- data/templates/src/include/errors.h.erb +15 -11
- data/templates/src/include/util/hb_foreach.h.erb +20 -0
- data/templates/src/parser_match_tags.c.erb +10 -4
- data/templates/src/visitor.c.erb +2 -2
- data/templates/template.rb +204 -29
- data/templates/wasm/error_helpers.cpp.erb +9 -5
- data/templates/wasm/nodes.cpp.erb +41 -4
- metadata +60 -16
- data/src/analyze.c +0 -1608
- data/src/element_source.c +0 -12
- data/src/include/util/hb_system.h +0 -9
- data/src/util/hb_system.c +0 -30
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module Herb
|
|
7
|
+
class Configuration
|
|
8
|
+
CONFIG_FILENAMES = [".herb.yml"].freeze
|
|
9
|
+
|
|
10
|
+
PROJECT_INDICATORS = [
|
|
11
|
+
".git",
|
|
12
|
+
".herb",
|
|
13
|
+
".herb.yml",
|
|
14
|
+
"Gemfile",
|
|
15
|
+
"package.json",
|
|
16
|
+
"Rakefile",
|
|
17
|
+
"README.md",
|
|
18
|
+
"*.gemspec",
|
|
19
|
+
"config/application.rb"
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
DEFAULTS_PATH = File.expand_path("defaults.yml", __dir__ || __FILE__).freeze
|
|
23
|
+
DEFAULTS = YAML.safe_load_file(DEFAULTS_PATH).freeze
|
|
24
|
+
|
|
25
|
+
attr_reader :config, :config_path, :project_root
|
|
26
|
+
|
|
27
|
+
def initialize(project_path = nil)
|
|
28
|
+
@start_path = project_path ? Pathname.new(project_path) : Pathname.pwd
|
|
29
|
+
@config_path, @project_root = find_config_file
|
|
30
|
+
@config = load_config
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def [](key)
|
|
34
|
+
@config[key.to_s]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def dig(*keys)
|
|
38
|
+
@config.dig(*keys.map(&:to_s))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def version
|
|
42
|
+
@config["version"]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def files
|
|
46
|
+
@config["files"] || {}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def file_include_patterns
|
|
50
|
+
files["include"] || DEFAULTS.dig("files", "include") || []
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def file_exclude_patterns
|
|
54
|
+
files["exclude"] || DEFAULTS.dig("files", "exclude") || []
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def linter
|
|
58
|
+
@config["linter"] || {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def engine
|
|
62
|
+
@config["engine"] || {}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def enabled_validators(overrides = {})
|
|
66
|
+
config = dig("engine", "validators") || {}
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
security: config.fetch("security", true),
|
|
70
|
+
nesting: config.fetch("nesting", true),
|
|
71
|
+
accessibility: config.fetch("accessibility", true),
|
|
72
|
+
}.merge(
|
|
73
|
+
overrides.to_h { |key, value| [key.to_sym, !!value] }
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def formatter
|
|
78
|
+
@config["formatter"] || {}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def include_patterns_for(tool)
|
|
82
|
+
tool_config = send(tool.to_s)
|
|
83
|
+
file_include_patterns + (tool_config["include"] || [])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def exclude_patterns_for(tool)
|
|
87
|
+
tool_config = send(tool.to_s)
|
|
88
|
+
file_exclude_patterns + (tool_config["exclude"] || [])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def linter_include_patterns
|
|
92
|
+
include_patterns_for(:linter)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def linter_exclude_patterns
|
|
96
|
+
exclude_patterns_for(:linter)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def formatter_include_patterns
|
|
100
|
+
include_patterns_for(:formatter)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def formatter_exclude_patterns
|
|
104
|
+
exclude_patterns_for(:formatter)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def enabled_for_path?(path, tool)
|
|
108
|
+
tool_config = send(tool.to_s)
|
|
109
|
+
tool_include = tool_config["include"] || []
|
|
110
|
+
tool_exclude = tool_config["exclude"] || []
|
|
111
|
+
|
|
112
|
+
if tool_include.any? && path_included?(path, tool_include)
|
|
113
|
+
return !path_excluded?(path, tool_exclude)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
exclude_patterns = exclude_patterns_for(tool)
|
|
117
|
+
|
|
118
|
+
!path_excluded?(path, exclude_patterns)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def linter_enabled_for_path?(path)
|
|
122
|
+
enabled_for_path?(path, :linter)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def formatter_enabled_for_path?(path)
|
|
126
|
+
enabled_for_path?(path, :formatter)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def path_excluded?(path, patterns)
|
|
130
|
+
patterns.any? { |pattern| File.fnmatch?(pattern, path, File::FNM_PATHNAME) }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def path_included?(path, patterns)
|
|
134
|
+
patterns.any? { |pattern| File.fnmatch?(pattern, path, File::FNM_PATHNAME) }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def find_files(search_path = nil)
|
|
138
|
+
search_path ||= @project_root || @start_path
|
|
139
|
+
expanded_path = File.expand_path(search_path.to_s)
|
|
140
|
+
|
|
141
|
+
all_files = file_include_patterns.flat_map do |pattern|
|
|
142
|
+
Dir[File.join(expanded_path, pattern)]
|
|
143
|
+
end.uniq
|
|
144
|
+
|
|
145
|
+
all_files.reject do |file|
|
|
146
|
+
relative = file.sub("#{expanded_path}/", "")
|
|
147
|
+
path_excluded?(relative, file_exclude_patterns)
|
|
148
|
+
end.sort
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def find_files_for_tool(tool, search_path = nil)
|
|
152
|
+
search_path ||= @project_root || @start_path
|
|
153
|
+
expanded_path = File.expand_path(search_path.to_s)
|
|
154
|
+
|
|
155
|
+
include_patterns = include_patterns_for(tool)
|
|
156
|
+
exclude_patterns = exclude_patterns_for(tool)
|
|
157
|
+
|
|
158
|
+
all_files = include_patterns.flat_map do |pattern|
|
|
159
|
+
Dir[File.join(expanded_path, pattern)]
|
|
160
|
+
end.uniq
|
|
161
|
+
|
|
162
|
+
all_files.reject do |file|
|
|
163
|
+
relative = file.sub("#{expanded_path}/", "")
|
|
164
|
+
path_excluded?(relative, exclude_patterns)
|
|
165
|
+
end.sort
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def find_files_for_linter(search_path = nil)
|
|
169
|
+
find_files_for_tool(:linter, search_path)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def find_files_for_formatter(search_path = nil)
|
|
173
|
+
find_files_for_tool(:formatter, search_path)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
class << self
|
|
177
|
+
def load(project_path = nil)
|
|
178
|
+
new(project_path)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def default
|
|
182
|
+
@default ||= new
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def default_file_patterns
|
|
186
|
+
DEFAULTS.dig("files", "include") || []
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def default_exclude_patterns
|
|
190
|
+
DEFAULTS.dig("files", "exclude") || []
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
def find_config_file
|
|
197
|
+
search_path = @start_path
|
|
198
|
+
search_path = search_path.parent if search_path.file?
|
|
199
|
+
|
|
200
|
+
while search_path.to_s != "/"
|
|
201
|
+
CONFIG_FILENAMES.each do |filename|
|
|
202
|
+
config_file = search_path / filename
|
|
203
|
+
return [config_file, search_path] if config_file.exist?
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
return [nil, search_path] if project_root?(search_path)
|
|
207
|
+
|
|
208
|
+
search_path = search_path.parent
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
[nil, @start_path]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def project_root?(path)
|
|
215
|
+
PROJECT_INDICATORS.any? do |indicator|
|
|
216
|
+
if indicator.include?("*")
|
|
217
|
+
Dir.glob(path / indicator).any?
|
|
218
|
+
else
|
|
219
|
+
(path / indicator).exist?
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def load_config
|
|
225
|
+
return deep_merge(DEFAULTS, {}) unless @config_path&.exist?
|
|
226
|
+
|
|
227
|
+
begin
|
|
228
|
+
user_config = YAML.safe_load_file(@config_path, permitted_classes: [Symbol]) || {}
|
|
229
|
+
deep_merge(DEFAULTS, user_config)
|
|
230
|
+
rescue Psych::SyntaxError => e
|
|
231
|
+
warn "Warning: Invalid YAML in #{@config_path}: #{e.message}"
|
|
232
|
+
deep_merge(DEFAULTS, {})
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def deep_merge(base, override, additive_keys: ["include", "exclude"])
|
|
237
|
+
base.merge(override) do |key, old_val, new_val|
|
|
238
|
+
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
239
|
+
deep_merge(old_val, new_val, additive_keys: additive_keys)
|
|
240
|
+
elsif old_val.is_a?(Array) && new_val.is_a?(Array) && additive_keys.include?(key)
|
|
241
|
+
old_val + new_val
|
|
242
|
+
else
|
|
243
|
+
new_val
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
files:
|
|
2
|
+
include:
|
|
3
|
+
- "**/*.herb"
|
|
4
|
+
- "**/*.html.erb"
|
|
5
|
+
- "**/*.html.herb"
|
|
6
|
+
- "**/*.html"
|
|
7
|
+
- "**/*.html+*.erb"
|
|
8
|
+
- "**/*.rhtml"
|
|
9
|
+
- "**/*.turbo_stream.erb"
|
|
10
|
+
|
|
11
|
+
exclude:
|
|
12
|
+
- "coverage/**/*"
|
|
13
|
+
- "log/**/*"
|
|
14
|
+
- "node_modules/**/*"
|
|
15
|
+
- "storage/**/*"
|
|
16
|
+
- "tmp/**/*"
|
|
17
|
+
- "vendor/**/*"
|
|
18
|
+
|
|
19
|
+
engine:
|
|
20
|
+
validators:
|
|
21
|
+
security: true
|
|
22
|
+
nesting: true
|
|
23
|
+
accessibility: true
|
|
24
|
+
|
|
25
|
+
linter:
|
|
26
|
+
enabled: true
|
|
27
|
+
rules: {}
|
|
28
|
+
|
|
29
|
+
formatter:
|
|
30
|
+
enabled: false
|
|
31
|
+
indentWidth: 2
|
|
32
|
+
maxLineLength: 80
|
data/lib/herb/engine/compiler.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Herb
|
|
|
22
22
|
def generate_output
|
|
23
23
|
optimized_tokens = optimize_tokens(@tokens)
|
|
24
24
|
|
|
25
|
-
optimized_tokens.each do |type, value, context|
|
|
25
|
+
optimized_tokens.each do |type, value, context, escaped|
|
|
26
26
|
case type
|
|
27
27
|
when :text
|
|
28
28
|
@engine.send(:add_text, value)
|
|
@@ -48,6 +48,8 @@ module Herb
|
|
|
48
48
|
when :expr_block_escaped
|
|
49
49
|
indicator = @escape ? "=" : "=="
|
|
50
50
|
@engine.send(:add_expression_block, indicator, value)
|
|
51
|
+
when :expr_block_end
|
|
52
|
+
@engine.send(:add_expression_block_end, value, escaped: escaped)
|
|
51
53
|
end
|
|
52
54
|
end
|
|
53
55
|
end
|
|
@@ -71,7 +73,27 @@ module Herb
|
|
|
71
73
|
visit_all(node.body)
|
|
72
74
|
visit(node.close_tag)
|
|
73
75
|
|
|
74
|
-
pop_context if
|
|
76
|
+
pop_context if ["script", "style"].include?(tag_name)
|
|
77
|
+
|
|
78
|
+
@element_stack.pop if tag_name
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def visit_html_conditional_element_node(node)
|
|
82
|
+
tag_name = node.tag_name&.value&.downcase
|
|
83
|
+
|
|
84
|
+
@element_stack.push(tag_name) if tag_name
|
|
85
|
+
|
|
86
|
+
if tag_name == "script"
|
|
87
|
+
push_context(:script_content)
|
|
88
|
+
elsif tag_name == "style"
|
|
89
|
+
push_context(:style_content)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
visit(node.open_conditional)
|
|
93
|
+
visit_all(node.body)
|
|
94
|
+
visit(node.close_conditional)
|
|
95
|
+
|
|
96
|
+
pop_context if ["script", "style"].include?(tag_name)
|
|
75
97
|
|
|
76
98
|
@element_stack.pop if tag_name
|
|
77
99
|
end
|
|
@@ -125,6 +147,10 @@ module Herb
|
|
|
125
147
|
add_text(node.tag_closing&.value)
|
|
126
148
|
end
|
|
127
149
|
|
|
150
|
+
def visit_html_omitted_close_tag_node(node)
|
|
151
|
+
# no-op
|
|
152
|
+
end
|
|
153
|
+
|
|
128
154
|
def visit_html_text_node(node)
|
|
129
155
|
add_text(node.content)
|
|
130
156
|
end
|
|
@@ -168,7 +194,9 @@ module Herb
|
|
|
168
194
|
end
|
|
169
195
|
|
|
170
196
|
def visit_erb_control_node(node, &_block)
|
|
171
|
-
|
|
197
|
+
if node.content
|
|
198
|
+
apply_trim(node, node.content.value.strip)
|
|
199
|
+
end
|
|
172
200
|
|
|
173
201
|
yield if block_given?
|
|
174
202
|
end
|
|
@@ -257,7 +285,7 @@ module Herb
|
|
|
257
285
|
end
|
|
258
286
|
|
|
259
287
|
visit_all(node.body)
|
|
260
|
-
|
|
288
|
+
visit_erb_block_end_node(node.end_node, escaped: should_escape)
|
|
261
289
|
else
|
|
262
290
|
visit_erb_control_node(node) do
|
|
263
291
|
visit_all(node.body)
|
|
@@ -266,6 +294,24 @@ module Herb
|
|
|
266
294
|
end
|
|
267
295
|
end
|
|
268
296
|
|
|
297
|
+
def visit_erb_block_end_node(node, escaped: false)
|
|
298
|
+
has_left_trim = node.tag_opening.value.start_with?("<%-")
|
|
299
|
+
|
|
300
|
+
remove_trailing_whitespace_from_last_token! if has_left_trim
|
|
301
|
+
|
|
302
|
+
code = node.content.value.strip
|
|
303
|
+
|
|
304
|
+
if at_line_start?
|
|
305
|
+
lspace = extract_and_remove_lspace!
|
|
306
|
+
rspace = " \n"
|
|
307
|
+
|
|
308
|
+
@tokens << [:expr_block_end, "#{lspace}#{code}#{rspace}", current_context, escaped]
|
|
309
|
+
@trim_next_whitespace = true
|
|
310
|
+
else
|
|
311
|
+
@tokens << [:expr_block_end, code, current_context, escaped]
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
269
315
|
def visit_erb_control_with_parts(node, *parts)
|
|
270
316
|
visit_erb_control_node(node) do
|
|
271
317
|
parts.each do |part|
|
|
@@ -320,13 +366,22 @@ module Herb
|
|
|
320
366
|
def process_erb_tag(node, skip_comment_check: false)
|
|
321
367
|
opening = node.tag_opening.value
|
|
322
368
|
|
|
323
|
-
|
|
369
|
+
if !skip_comment_check && erb_comment?(opening)
|
|
370
|
+
has_left_trim = opening.start_with?("<%-")
|
|
371
|
+
remove_trailing_whitespace_from_last_token! if has_left_trim
|
|
372
|
+
|
|
373
|
+
if at_line_start?
|
|
374
|
+
extract_and_remove_lspace!
|
|
375
|
+
@trim_next_whitespace = true
|
|
376
|
+
end
|
|
377
|
+
return
|
|
378
|
+
end
|
|
324
379
|
return if erb_graphql?(opening)
|
|
325
380
|
|
|
326
381
|
code = node.content.value.strip
|
|
327
382
|
|
|
328
383
|
if erb_output?(opening)
|
|
329
|
-
process_erb_output(opening, code)
|
|
384
|
+
process_erb_output(node, opening, code)
|
|
330
385
|
else
|
|
331
386
|
apply_trim(node, code)
|
|
332
387
|
end
|
|
@@ -370,7 +425,7 @@ module Herb
|
|
|
370
425
|
current_text = ""
|
|
371
426
|
current_context = nil
|
|
372
427
|
|
|
373
|
-
compacted.each do |type, value, context|
|
|
428
|
+
compacted.each do |type, value, context, escaped|
|
|
374
429
|
if type == :text
|
|
375
430
|
current_text += value
|
|
376
431
|
current_context ||= context
|
|
@@ -382,7 +437,7 @@ module Herb
|
|
|
382
437
|
current_context = nil
|
|
383
438
|
end
|
|
384
439
|
|
|
385
|
-
optimized << [type, value, context]
|
|
440
|
+
optimized << [type, value, context, escaped]
|
|
386
441
|
end
|
|
387
442
|
end
|
|
388
443
|
|
|
@@ -441,9 +496,11 @@ module Herb
|
|
|
441
496
|
search_index >= 0 ? tokens[search_index] : nil
|
|
442
497
|
end
|
|
443
498
|
|
|
444
|
-
def process_erb_output(opening, code)
|
|
499
|
+
def process_erb_output(node, opening, code)
|
|
500
|
+
has_right_trim = node.tag_closing&.value == "-%>"
|
|
445
501
|
should_escape = should_escape_output?(opening)
|
|
446
502
|
add_expression_with_escaping(code, should_escape)
|
|
503
|
+
@trim_next_whitespace = true if has_right_trim
|
|
447
504
|
end
|
|
448
505
|
|
|
449
506
|
def should_escape_output?(opening)
|
|
@@ -464,10 +521,20 @@ module Herb
|
|
|
464
521
|
@tokens.last[0] != :text ||
|
|
465
522
|
@tokens.last[1].empty? ||
|
|
466
523
|
@tokens.last[1].end_with?("\n") ||
|
|
467
|
-
@tokens.last[1] =~ /\A[ \t]+\z/ ||
|
|
524
|
+
(@tokens.last[1] =~ /\A[ \t]+\z/ && preceding_token_ends_with_newline?) ||
|
|
468
525
|
@tokens.last[1] =~ /\n[ \t]+\z/
|
|
469
526
|
end
|
|
470
527
|
|
|
528
|
+
def preceding_token_ends_with_newline?
|
|
529
|
+
return true unless @tokens.length >= 2
|
|
530
|
+
|
|
531
|
+
preceding = @tokens[-2]
|
|
532
|
+
return false if [:expr, :expr_escaped, :expr_block, :expr_block_escaped, :expr_block_end].include?(preceding[0])
|
|
533
|
+
return true unless preceding[0] == :text
|
|
534
|
+
|
|
535
|
+
preceding[1].end_with?("\n")
|
|
536
|
+
end
|
|
537
|
+
|
|
471
538
|
def extract_lspace
|
|
472
539
|
return "" unless @tokens.last && @tokens.last[0] == :text
|
|
473
540
|
|
|
@@ -501,7 +568,7 @@ module Herb
|
|
|
501
568
|
|
|
502
569
|
if at_line_start?
|
|
503
570
|
lspace = extract_and_remove_lspace!
|
|
504
|
-
rspace = " \n"
|
|
571
|
+
rspace = Herb::Engine.heredoc?(code) ? "\n" : " \n"
|
|
505
572
|
|
|
506
573
|
@tokens << [:code, "#{lspace}#{code}#{rspace}", current_context]
|
|
507
574
|
@trim_next_whitespace = true
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
# typed: false
|
|
2
3
|
|
|
3
4
|
module Herb
|
|
4
5
|
class Engine
|
|
@@ -31,6 +32,7 @@ module Herb
|
|
|
31
32
|
@in_html_comment = false
|
|
32
33
|
@in_html_doctype = false
|
|
33
34
|
@erb_nodes_to_wrap = [] #: Array[Herb::AST::ERBContentNode]
|
|
35
|
+
@top_level_elements = [] #: Array[Herb::AST::HTMLElementNode]
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def visit_document_node(node)
|
|
@@ -149,8 +151,6 @@ module Herb
|
|
|
149
151
|
end
|
|
150
152
|
|
|
151
153
|
def find_top_level_elements(document_node)
|
|
152
|
-
@top_level_elements = [] #: Array[Herb::AST::HTMLElementNode]
|
|
153
|
-
|
|
154
154
|
document_node.children.each do |child|
|
|
155
155
|
@top_level_elements << child if child.is_a?(Herb::AST::HTMLElementNode)
|
|
156
156
|
end
|
|
@@ -339,7 +339,9 @@ module Herb
|
|
|
339
339
|
excluded_tags = ["script", "style", "head", "textarea", "pre", "svg", "math"]
|
|
340
340
|
return true if excluded_tags.any? { |tag| @element_stack.include?(tag) }
|
|
341
341
|
|
|
342
|
-
|
|
342
|
+
if @erb_block_stack.any? { |node| javascript_tag?(node.content.value.strip) || include_debug_disable_comment?(node.content.value.strip) }
|
|
343
|
+
return true
|
|
344
|
+
end
|
|
343
345
|
|
|
344
346
|
false
|
|
345
347
|
end
|
|
@@ -390,6 +392,14 @@ module Herb
|
|
|
390
392
|
|
|
391
393
|
false
|
|
392
394
|
end
|
|
395
|
+
|
|
396
|
+
def include_debug_disable_comment?(code)
|
|
397
|
+
cleaned_code = code.strip.gsub(/\s+/, " ")
|
|
398
|
+
|
|
399
|
+
return true if cleaned_code.match?(/#\s*herb:debug\sdisable\s*$/)
|
|
400
|
+
|
|
401
|
+
false
|
|
402
|
+
end
|
|
393
403
|
end
|
|
394
404
|
end
|
|
395
405
|
end
|
|
@@ -214,12 +214,9 @@ module Herb
|
|
|
214
214
|
output << " Details: #{error.error_message}\n"
|
|
215
215
|
output << " Suggestion: Check your Ruby syntax inside the ERB tag\n"
|
|
216
216
|
|
|
217
|
-
when Herb::Errors::
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
output << " Closing quote: #{error.closing_quote.value}\n"
|
|
221
|
-
output << " Suggestion: Use matching quotes for attribute values\n"
|
|
222
|
-
end
|
|
217
|
+
when Herb::Errors::MissingAttributeValueError
|
|
218
|
+
output << " Attribute: #{error.attribute_name}\n"
|
|
219
|
+
output << " Suggestion: Add a value after the equals sign or remove the equals sign\n"
|
|
223
220
|
end
|
|
224
221
|
|
|
225
222
|
output
|
|
@@ -229,7 +226,8 @@ module Herb
|
|
|
229
226
|
case error
|
|
230
227
|
when Herb::Errors::MissingClosingTagError,
|
|
231
228
|
Herb::Errors::TagNamesMismatchError,
|
|
232
|
-
Herb::Errors::UnclosedElementError
|
|
229
|
+
Herb::Errors::UnclosedElementError,
|
|
230
|
+
Herb::Errors::MissingAttributeValueError
|
|
233
231
|
true
|
|
234
232
|
else
|
|
235
233
|
false
|
|
@@ -244,6 +242,8 @@ module Herb
|
|
|
244
242
|
"← Tag mismatch"
|
|
245
243
|
when Herb::Errors::UnclosedElementError
|
|
246
244
|
"← Unclosed element"
|
|
245
|
+
when Herb::Errors::MissingAttributeValueError
|
|
246
|
+
"← Missing attribute value"
|
|
247
247
|
else
|
|
248
248
|
""
|
|
249
249
|
end
|
|
@@ -419,8 +419,12 @@ module Herb
|
|
|
419
419
|
end
|
|
420
420
|
when Herb::Errors::RubyParseError
|
|
421
421
|
"Check your Ruby syntax inside the ERB tag"
|
|
422
|
-
when Herb::Errors::
|
|
423
|
-
|
|
422
|
+
when Herb::Errors::MissingAttributeValueError
|
|
423
|
+
if error.attribute_name
|
|
424
|
+
"Add a value after the equals sign for '#{error.attribute_name}' or remove the equals sign"
|
|
425
|
+
else
|
|
426
|
+
"Add a value after the equals sign or remove the equals sign"
|
|
427
|
+
end
|
|
424
428
|
end
|
|
425
429
|
end
|
|
426
430
|
end
|
|
@@ -9,12 +9,12 @@ module Herb
|
|
|
9
9
|
Herb::Errors::UnexpectedTokenError,
|
|
10
10
|
Herb::Errors::UnexpectedError,
|
|
11
11
|
Herb::Errors::RubyParseError,
|
|
12
|
-
Herb::Errors::QuotesMismatchError,
|
|
13
12
|
Herb::Errors::TagNamesMismatchError,
|
|
14
13
|
Herb::Errors::VoidElementClosingTagError,
|
|
15
14
|
Herb::Errors::UnclosedElementError,
|
|
16
15
|
Herb::Errors::MissingClosingTagError,
|
|
17
|
-
Herb::Errors::MissingOpeningTagError
|
|
16
|
+
Herb::Errors::MissingOpeningTagError,
|
|
17
|
+
Herb::Errors::MissingAttributeValueError
|
|
18
18
|
].freeze
|
|
19
19
|
|
|
20
20
|
def initialize(source, errors, filename: nil)
|
|
@@ -729,8 +729,12 @@ module Herb
|
|
|
729
729
|
end
|
|
730
730
|
when Herb::Errors::RubyParseError
|
|
731
731
|
"Fix Ruby syntax: Check your Ruby syntax inside the ERB tag"
|
|
732
|
-
when Herb::Errors::
|
|
733
|
-
|
|
732
|
+
when Herb::Errors::MissingAttributeValueError
|
|
733
|
+
if error.respond_to?(:attribute_name) && error.attribute_name
|
|
734
|
+
"Add attribute value: Add a value after the equals sign for '#{error.attribute_name}' or remove the equals sign"
|
|
735
|
+
else
|
|
736
|
+
"Add attribute value: Add a value after the equals sign or remove the equals sign"
|
|
737
|
+
end
|
|
734
738
|
else
|
|
735
739
|
message = error.respond_to?(:message) ? error.message : error.to_s
|
|
736
740
|
"Fix error: #{message}"
|
|
@@ -747,10 +751,10 @@ module Herb
|
|
|
747
751
|
"← Unclosed element"
|
|
748
752
|
when Herb::Errors::VoidElementClosingTagError
|
|
749
753
|
"← Void element cannot be closed"
|
|
750
|
-
when Herb::Errors::QuotesMismatchError
|
|
751
|
-
"← Quote mismatch"
|
|
752
754
|
when Herb::Errors::RubyParseError
|
|
753
755
|
"← Ruby syntax error"
|
|
756
|
+
when Herb::Errors::MissingAttributeValueError
|
|
757
|
+
"← Missing attribute value"
|
|
754
758
|
end
|
|
755
759
|
end
|
|
756
760
|
|
|
@@ -3,14 +3,19 @@
|
|
|
3
3
|
module Herb
|
|
4
4
|
class Engine
|
|
5
5
|
class Validator < Herb::Visitor
|
|
6
|
-
attr_reader :diagnostics
|
|
6
|
+
attr_reader :diagnostics, :enabled
|
|
7
7
|
|
|
8
|
-
def initialize
|
|
9
|
-
super
|
|
8
|
+
def initialize(enabled: true)
|
|
9
|
+
super()
|
|
10
10
|
|
|
11
|
+
@enabled = enabled
|
|
11
12
|
@diagnostics = []
|
|
12
13
|
end
|
|
13
14
|
|
|
15
|
+
def enabled?
|
|
16
|
+
@enabled
|
|
17
|
+
end
|
|
18
|
+
|
|
14
19
|
def validate(node)
|
|
15
20
|
visit(node)
|
|
16
21
|
end
|
|
@@ -31,7 +31,7 @@ module Herb
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def validate_no_block_elements_in_paragraph(node)
|
|
34
|
-
block_elements =
|
|
34
|
+
block_elements = ["div", "section", "article", "header", "footer", "nav", "aside", "p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "dl", "table", "form"]
|
|
35
35
|
|
|
36
36
|
node.body.each do |child|
|
|
37
37
|
next unless child.is_a?(Herb::AST::HTMLElementNode)
|
|
@@ -58,7 +58,7 @@ module Herb
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def validate_no_interactive_in_button(node)
|
|
61
|
-
interactive_elements =
|
|
61
|
+
interactive_elements = ["a", "button", "input", "select", "textarea"]
|
|
62
62
|
|
|
63
63
|
node.body.each do |child|
|
|
64
64
|
next unless child.is_a?(Herb::AST::HTMLElementNode)
|