haml 6.4.0 → 7.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 313ffec5cc79d2b238173fde9769ac60d26b54bf15d3deda68adc84d0849620b
4
- data.tar.gz: 143e9115612a2555a4ce34c817745e330548a7eaedf98bc819ead44b2156f8ad
3
+ metadata.gz: 8cf8d7cf95a5096090897276b03dd8073e73705fa1b880bba96af5dceb3b2c55
4
+ data.tar.gz: 4d1e1ff1bd53361d0a31c78533a838e60e52d10c5646ff0d8bbd87a552ccc658
5
5
  SHA512:
6
- metadata.gz: 117f3811b514f8abd4142cdc8022e282e5a5434c5c0f6904e7eaff7655f58894e3486c999282f0140b6011016b52b139036071792c3ba33494de73342d512d77
7
- data.tar.gz: 99e76cc2438c8ba70aaf39b3ccc0df9b610e3ac06de35b8865c2f2ce5aa692f9642791b863602eef5e382d60224fdb44a143074c11f072ccad7556ae1461b261
6
+ metadata.gz: 3bbfe67377d06702fa3240048fd25410f885f05a503d4e8e7793f0ccbba9ffb99e601d46068150d2170c11a3807660571fe26f337becfc155758f05cc470bc40
7
+ data.tar.gz: '01159a17fda467397309cfa38de5ad7b1e8820d506447b87221a8cdc087f02e8546cb1d2ad9a358d4ad0fa0c0b29c60b0a03bcf692e2fa551d748111701d8adc'
@@ -17,14 +17,10 @@ jobs:
17
17
  fail-fast: false
18
18
  matrix:
19
19
  ruby:
20
- - '2.5'
21
- - '2.6'
22
- - '2.7'
23
- - '3.0'
24
- - '3.1'
25
20
  - '3.2'
26
21
  - '3.3'
27
22
  - '3.4'
23
+ - '4.0'
28
24
  - ruby-head
29
25
  - jruby
30
26
  - truffleruby
data/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # Haml Changelog
2
2
 
3
+ ## 7.5.1
4
+
5
+ * Improve frozen strings management https://github.com/haml/haml/pull/1205
6
+
7
+ ## 7.5.0
8
+
9
+ * Compile a multi-line attribute hash statically https://github.com/haml/haml/pull/1222
10
+ * Errors from multi-line attributes will be reported on the tag's line instead of the expression that raised the error.
11
+
12
+ ## 7.4.1
13
+
14
+ * Build the preserve regex once per tag list instead of per call https://github.com/haml/haml/pull/1220
15
+
16
+ ## 7.4.0
17
+
18
+ * `Haml::BOOLEAN_ATTRIBUTES` is changed from Array to Set https://github.com/haml/haml/pull/1216
19
+ * A guard on `respond_to?(:html_safe?)` is removed from `Haml::Util.escape_html_safe` https://github.com/haml/haml/pull/1217
20
+ * Keep interpolated text in the source encoding, fixing the `Encoding::CompatibilityError` remaining on an ASCII-8BIT
21
+ template source that mixes plain text and interpolation with non-ASCII characters https://github.com/haml/haml/issues/1218
22
+
23
+ ## 7.3.1
24
+
25
+ * Keep Prism-derived fragments in the source encoding, fixing `Encoding::CompatibilityError` on an ASCII-8BIT
26
+ template source with non-ASCII characters (regression in 7.3.0) https://github.com/haml/haml/issues/1218
27
+
28
+ ## 7.3.0
29
+
30
+ * Replace Ripper with Prism https://github.com/haml/haml/pull/1214
31
+ * Interpolate `#@ivar`, `#$gvar` and `#@@cvar` in string literals instead of dropping them
32
+ * Keep an escaped delimiter of a percent literal, so `= %q{a\}b}` renders `a}b`
33
+ * Deprecate `Haml::AttributeParser.available?`, which is now always true and will be removed in the future
34
+
35
+ ## 7.2.2
36
+
37
+ * Remove obsolete TruffleRuby compatibility skips https://github.com/haml/haml/pull/1210
38
+
39
+ ## 7.2.1
40
+
41
+ * Do not rely on Ripper quirk in parsing old-style Haml attributes https://github.com/haml/haml/pull/1212
42
+
43
+ ## 7.2.0
44
+
45
+ * Do not require rails when haml is required https://github.com/haml/haml/pull/1201
46
+ * Point `changelog_uri` to GitHub Releases https://github.com/haml/haml/pull/1202
47
+
48
+ ## 7.1.0
49
+
50
+ * Support xhtml format for boolean nested data attributes https://github.com/haml/haml/pull/1200
51
+
52
+ ## 7.0.2
53
+
54
+ * Replace usages of `=~` with `.match?` where possible https://github.com/haml/haml/pull/1196
55
+ * Add source code URI to gemspec metadata https://github.com/haml/haml/pull/1197
56
+
57
+ ## 7.0.1
58
+
59
+ * Use `Regexp#match?` predicate where possible https://github.com/haml/haml/pull/1194
60
+ * Fix the 'Changelog' link on rubygems.org/gems/haml https://github.com/haml/haml/pull/1192
61
+
62
+ ## 7.0.0
63
+
64
+ * Change the default `attr_quote` from `'` to `"` https://github.com/haml/haml/issues/1188
65
+ * Bump required Ruby version to 3.2 https://github.com/haml/haml/issues/1176
66
+
3
67
  ## 6.4.0
4
68
 
5
69
  * Authorize characters in attribute name for vuejs https://github.com/haml/haml/pull/1172
data/Gemfile CHANGED
@@ -10,25 +10,17 @@ gemspec
10
10
 
11
11
  gem 'benchmark-ips', '2.3.0'
12
12
  gem 'maxitest'
13
- gem 'pry'
13
+ gem 'base64'
14
+ gem 'bigdecimal'
15
+ gem 'mutex_m'
14
16
 
15
- if /java/ === RUBY_PLATFORM # JRuby
17
+ if /java/.match?(RUBY_PLATFORM) # JRuby
16
18
  gem 'pandoc-ruby'
19
+ gem 'rdoc', '< 8' # rdoc 8 depends on rbs, whose native extension doesn't build on JRuby
17
20
  else
18
21
  gem 'redcarpet'
19
22
 
20
- if RUBY_PLATFORM !~ /mswin|mingw/ && RUBY_ENGINE != 'truffleruby'
23
+ if RUBY_PLATFORM !~ /mswin|mingw/
21
24
  gem 'stackprof'
22
25
  end
23
26
  end
24
-
25
- if RUBY_VERSION < '2.6'
26
- gem 'rake-compiler', '< 1.2.4'
27
- end
28
-
29
- # Temporary workaround to ensure Ruby 2.5 and 2.6 can pass tests with Rails 6.1
30
- # Reference: https://github.com/rails/rails/issues/54260
31
- # TODO: Remove this workaround when dropping support for Rails versions below 7.1
32
- if RUBY_VERSION < '2.7'
33
- gem 'concurrent-ruby', '< 1.3.5'
34
- end
data/README.md CHANGED
@@ -157,7 +157,7 @@ on a specific area:
157
157
  ruby -Itest test/helper_test.rb -n test_buffer_access
158
158
  ~~~
159
159
 
160
- Haml currently supports Ruby 2.0.0 and higher, so please make sure your changes run on 2.0+.
160
+ Haml currently supports Ruby 3.2.0 and higher, so please make sure your changes run on 3.2+.
161
161
 
162
162
  ## Team
163
163
 
data/REFERENCE.md CHANGED
@@ -320,7 +320,7 @@ or using `true` and `false`:
320
320
  %input(selected=true)
321
321
 
322
322
  This feature works only for attributes that are included in
323
- [`Haml::BOOLEAN_ATTRIBUTES`](https://github.com/haml/haml/blob/main/lib/haml/attribute_compiler.rb#L8),
323
+ [`Haml::BOOLEAN_ATTRIBUTES`](https://github.com/haml/haml/blob/main/lib/haml/attribute_compiler.rb#L9),
324
324
  as well as `data-` and `aria-` attributes.
325
325
 
326
326
  %input{'data-hidden' => false}
data/bin/console CHANGED
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require 'hamlit'
4
+ require 'haml'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
8
8
 
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- require 'pry'
11
- Pry.start
9
+ require 'irb'
10
+ IRB.start
data/bin/stackprof CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require 'hamlit'
4
+ require 'haml'
5
5
  require 'stackprof'
6
6
 
7
7
  def open_flamegraph(report)
@@ -20,7 +20,7 @@ end
20
20
 
21
21
  haml = File.read(ARGV.first)
22
22
  StackProf.start(mode: :wall, interval: 1, raw: false)
23
- Hamlit::Engine.new.call(haml)
23
+ Haml::Engine.new.call(haml)
24
24
  StackProf.stop
25
25
 
26
26
  report = StackProf::Report.new(StackProf.results)
data/haml.gemspec CHANGED
@@ -21,10 +21,14 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.metadata = { 'rubygems_mfa_required' => 'true' }
23
23
 
24
- spec.metadata["changelog_uri"] = spec.homepage + "/blob/main/CHANGELOG.md"
24
+ spec.metadata["bug_tracker_uri"] = "https://github.com/haml/haml/issues"
25
+ spec.metadata["changelog_uri"] = "https://github.com/haml/haml/blob/main/CHANGELOG.md"
26
+ spec.metadata["homepage_uri"] = "https://haml.info/"
27
+ spec.metadata["source_code_uri"] = "https://github.com/haml/haml"
25
28
 
26
- spec.required_ruby_version = '>= 2.1.0'
29
+ spec.required_ruby_version = '>= 3.2.0'
27
30
 
31
+ spec.add_dependency 'prism', '>= 1.1.0'
28
32
  spec.add_dependency 'temple', '>= 0.8.2'
29
33
  spec.add_dependency 'thor'
30
34
  spec.add_dependency 'tilt'
@@ -33,7 +37,6 @@ Gem::Specification.new do |spec|
33
37
  spec.add_development_dependency 'bundler'
34
38
  spec.add_development_dependency 'coffee-script'
35
39
  spec.add_development_dependency 'erubi'
36
- spec.add_development_dependency 'haml', '>= 5'
37
40
  spec.add_development_dependency 'less'
38
41
  spec.add_development_dependency 'minitest-reporters', '~> 1.1'
39
42
  spec.add_development_dependency 'rails', '>= 4.0'
@@ -16,13 +16,15 @@ module Haml::AttributeBuilder
16
16
  when 'class'
17
17
  buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}"
18
18
  when 'data'
19
- buf << build_data(escape_attrs, quote, *hash[key])
19
+ buf << build_data(escape_attrs, quote, format, *hash[key])
20
20
  when 'aria'
21
- buf << build_aria(escape_attrs, quote, *hash[key])
22
- when *Haml::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
23
- build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
21
+ buf << build_aria(escape_attrs, quote, format, *hash[key])
24
22
  else
25
- buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
23
+ if boolean_attribute?(key)
24
+ build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
25
+ else
26
+ buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
27
+ end
26
28
  end
27
29
  end
28
30
  buf.join
@@ -62,17 +64,21 @@ module Haml::AttributeBuilder
62
64
  escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
63
65
  end
64
66
 
65
- def build_data(escape_attrs, quote, *hashes)
66
- build_data_attribute(:data, escape_attrs, quote, *hashes)
67
+ def build_data(escape_attrs, quote, format, *hashes)
68
+ build_data_attribute(:data, escape_attrs, quote, format, *hashes)
67
69
  end
68
70
 
69
- def build_aria(escape_attrs, quote, *hashes)
70
- build_data_attribute(:aria, escape_attrs, quote, *hashes)
71
+ def build_aria(escape_attrs, quote, format, *hashes)
72
+ build_data_attribute(:aria, escape_attrs, quote, format, *hashes)
71
73
  end
72
74
 
73
75
  private
74
76
 
75
- def build_data_attribute(key, escape_attrs, quote, *hashes)
77
+ def boolean_attribute?(key)
78
+ Haml::BOOLEAN_ATTRIBUTES.include?(key) || key.start_with?('data-', 'aria-')
79
+ end
80
+
81
+ def build_data_attribute(key, escape_attrs, quote, format, *hashes)
76
82
  attrs = []
77
83
  if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) }
78
84
  data_value = merge_all_attrs(hashes)
@@ -84,7 +90,7 @@ module Haml::AttributeBuilder
84
90
  hash.sort_by(&:first).each do |key, value|
85
91
  case value
86
92
  when true
87
- attrs << " #{key}"
93
+ build_boolean!(escape_attrs, quote, format, attrs, key, value)
88
94
  when nil, false
89
95
  # noop
90
96
  else
@@ -98,14 +104,18 @@ module Haml::AttributeBuilder
98
104
  flattened = {}
99
105
 
100
106
  attributes.each do |key, value|
107
+ # Recursing into a hash which contains itself would never end.
108
+ next if value.equal?(attributes)
109
+
101
110
  case value
102
- when attributes
103
111
  when Hash
104
112
  flatten_attributes(value).each do |k, v|
105
113
  if k.nil?
106
114
  flattened[key] = v
107
115
  else
108
- flattened["#{key}-#{k.to_s.tr('_', '-')}"] = v
116
+ k = k.is_a?(Symbol) ? k.name : k.to_s
117
+ k = k.tr('_', '-') if k.include?('_')
118
+ flattened["#{key}-#{k}"] = v
109
119
  end
110
120
  end
111
121
  else
@@ -122,7 +132,8 @@ module Haml::AttributeBuilder
122
132
  raise ArgumentError, "Non-hash object is given to attributes!"
123
133
  end
124
134
  hash.each do |key, value|
125
- key = key.to_s
135
+ # Symbol#name hands back the interned String; to_s allocates one per render.
136
+ key = key.is_a?(Symbol) ? key.name : key.to_s
126
137
  case key
127
138
  when 'id', 'class', 'data', 'aria'
128
139
  merged[key] ||= []
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
+ require 'set'
2
3
  require 'haml/attribute_builder'
3
4
  require 'haml/attribute_parser'
4
5
  require 'haml/ruby_expression'
6
+ require 'haml/temple_line_counter'
5
7
 
6
8
  module Haml
7
- # The list of boolean attributes. You may add custom attributes to this constant.
8
- BOOLEAN_ATTRIBUTES = %w[disabled readonly multiple checked autobuffer
9
+ # The set of boolean attributes. You may add custom attributes to this constant.
10
+ BOOLEAN_ATTRIBUTES = Set.new(%w[disabled readonly multiple checked autobuffer
9
11
  autoplay controls loop selected hidden scoped async
10
12
  defer reversed ismap seamless muted required
11
13
  autofocus novalidate formnovalidate open pubdate
12
14
  itemscope allowfullscreen default inert sortable
13
- truespeed typemustmatch download]
15
+ truespeed typemustmatch download])
14
16
 
15
17
  class AttributeCompiler
16
18
  def initialize(identity, options)
@@ -22,15 +24,16 @@ module Haml
22
24
 
23
25
  def compile(node)
24
26
  hashes = []
25
- if node.value[:object_ref] != :nil || !AttributeParser.available?
26
- return runtime_compile(node)
27
- end
28
- [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
27
+ return runtime_compile(node) if node.value[:object_ref] != :nil
28
+ dynamic_attributes = node.value[:dynamic_attributes]
29
+ [dynamic_attributes.new, dynamic_attributes.old].compact.each do |attribute_str|
29
30
  hash = AttributeParser.parse(attribute_str)
30
31
  return runtime_compile(node) if hash.nil? || hash.any? { |_key, value| value.empty? }
31
32
  hashes << hash
32
33
  end
33
- static_compile(node.value[:attributes], hashes)
34
+ temple = static_compile(node.value[:attributes], hashes)
35
+ restore_newlines!(temple, dynamic_attributes)
36
+ temple
34
37
  end
35
38
 
36
39
  private
@@ -57,15 +60,24 @@ module Haml
57
60
  compile_class!(temple, key, values)
58
61
  when 'data', 'aria'
59
62
  compile_data!(temple, key, values)
60
- when *BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
61
- compile_boolean!(temple, key, values)
62
63
  else
63
- compile_common!(temple, key, values)
64
+ if BOOLEAN_ATTRIBUTES.include?(key) || key.start_with?('data-', 'aria-')
65
+ compile_boolean!(temple, key, values)
66
+ else
67
+ compile_common!(temple, key, values)
68
+ end
64
69
  end
65
70
  end
66
71
  temple
67
72
  end
68
73
 
74
+ # ChildrenCompiler counts on the tag spanning as many lines as its attribute source does.
75
+ def restore_newlines!(temple, dynamic_attributes)
76
+ missing = dynamic_attributes.newline_count
77
+ missing -= TempleLineCounter.count_lines(temple) if missing > 0
78
+ missing.times { temple << [:newline] }
79
+ end
80
+
69
81
  def compile_id!(temple, key, values)
70
82
  build_code = attribute_builder(:id, values)
71
83
  if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
@@ -85,7 +97,7 @@ module Haml
85
97
  end
86
98
 
87
99
  def compile_data!(temple, key, values)
88
- args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", values.map { |v| literal_for(v) }]
100
+ args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect, values.map { |v| literal_for(v) }]
89
101
  build_code = "::Haml::AttributeBuilder.build_#{key}(#{args.join(', ')})"
90
102
 
91
103
  if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
@@ -1,34 +1,36 @@
1
1
  # frozen_string_literal: true
2
- require 'haml/ruby_expression'
2
+ require 'prism'
3
3
 
4
4
  module Haml
5
5
  class AttributeParser
6
- class ParseSkip < StandardError
7
- end
8
-
9
- # @return [TrueClass, FalseClass] - return true if AttributeParser.parse can be used.
6
+ # @deprecated Prism is a hard dependency, so this is always true. Haml itself no longer
7
+ # asks, and this will be removed in the future.
8
+ # @return [TrueClass] - return true if AttributeParser.parse can be used.
10
9
  def self.available?
11
- # TruffleRuby doesn't have Ripper.lex
12
- defined?(Ripper) && Ripper.respond_to?(:lex) && Temple::StaticAnalyzer.available?
10
+ true
13
11
  end
14
12
 
15
13
  def self.parse(text)
16
14
  self.new.parse(text)
17
15
  end
18
16
 
17
+ # @return [Hash,nil] - keys and values are the attribute source as written, or nil if
18
+ # the text is not a Hash literal whose keys are all static, or if it holds a heredoc.
19
19
  def parse(text)
20
20
  exp = wrap_bracket(text)
21
- return if Temple::StaticAnalyzer.syntax_error?(exp)
21
+ node = hash_node(exp)
22
+ return if node.nil? || contains_heredoc?(exp, node)
22
23
 
23
24
  hash = {}
24
- tokens = Ripper.lex(exp)[1..-2] || []
25
- each_attr(tokens) do |attr_tokens|
26
- key = parse_key!(attr_tokens)
27
- hash[key] = attr_tokens.map { |t| t[2] }.join.strip
25
+ node.elements.each do |element|
26
+ return unless element.is_a?(Prism::AssocNode)
27
+
28
+ key = static_key(element.key)
29
+ return if key.nil?
30
+
31
+ hash[in_source_encoding(key, exp)] = in_source_encoding(value_source(element.value), exp)
28
32
  end
29
33
  hash
30
- rescue ParseSkip
31
- nil
32
34
  end
33
35
 
34
36
  private
@@ -39,78 +41,50 @@ module Haml
39
41
  "{#{text}}"
40
42
  end
41
43
 
42
- def parse_key!(tokens)
43
- _, type, str = tokens.shift
44
- case type
45
- when :on_sp
46
- parse_key!(tokens)
47
- when :on_label
48
- str.tr(':', '')
49
- when :on_symbeg
50
- _, _, key = tokens.shift
51
- assert_type!(tokens.shift, :on_tstring_end) if str != ':'
52
- skip_until_hash_rocket!(tokens)
53
- key
54
- when :on_tstring_beg
55
- _, _, key = tokens.shift
56
- next_token = tokens.shift
57
- unless next_token[1] == :on_label_end
58
- assert_type!(next_token, :on_tstring_end)
59
- skip_until_hash_rocket!(tokens)
60
- end
61
- key
62
- else
63
- raise ParseSkip
64
- end
44
+ def hash_node(exp)
45
+ # partial_script, because an attribute may legitimately call `yield` and the like:
46
+ # a template is compiled into a method body.
47
+ result = Prism.parse(exp, partial_script: true)
48
+ return if result.failure?
49
+
50
+ statements = result.value.statements.body
51
+ return if statements.size != 1
52
+
53
+ node = statements.first
54
+ node if node.is_a?(Prism::HashNode)
65
55
  end
66
56
 
67
- def assert_type!(token, type)
68
- raise ParseSkip if token[1] != type
57
+ # A heredoc's body lies outside its node, so the value's slice would not be the value.
58
+ # Its opener always spells `<<`, which spares the tree walk for nearly every hash.
59
+ def contains_heredoc?(exp, node)
60
+ exp.include?('<<') && !node.breadth_first_search { |n| n.respond_to?(:heredoc?) && n.heredoc? }.nil?
69
61
  end
70
62
 
71
- def skip_until_hash_rocket!(tokens)
72
- until tokens.empty?
73
- _, type, str = tokens.shift
74
- break if type == :on_op && str == '=>'
63
+ # The key as written between its delimiters, not unescaped: an escape has to reach the
64
+ # attribute name as the source spelled it, like the `\0` of `{ "a\0b" => 1 }`.
65
+ def static_key(key)
66
+ case key
67
+ when Prism::SymbolNode then key.value_loc&.slice
68
+ when Prism::StringNode then key.content_loc&.slice
75
69
  end
76
70
  end
77
71
 
78
- def each_attr(tokens)
79
- attr_tokens = []
80
- open_tokens = Hash.new { |h, k| h[k] = 0 }
81
-
82
- tokens.each do |token|
83
- _, type, _ = token
84
- case type
85
- when :on_comma
86
- if open_tokens.values.all?(&:zero?)
87
- yield(attr_tokens)
88
- attr_tokens = []
89
- next
90
- end
91
- when :on_lbracket
92
- open_tokens[:array] += 1
93
- when :on_rbracket
94
- open_tokens[:array] -= 1
95
- when :on_lbrace
96
- open_tokens[:block] += 1
97
- when :on_rbrace
98
- open_tokens[:block] -= 1
99
- when :on_lparen
100
- open_tokens[:paren] += 1
101
- when :on_rparen
102
- open_tokens[:paren] -= 1
103
- when :on_embexpr_beg
104
- open_tokens[:embexpr] += 1
105
- when :on_embexpr_end
106
- open_tokens[:embexpr] -= 1
107
- when :on_sp
108
- next if attr_tokens.empty?
109
- end
110
-
111
- attr_tokens << token
112
- end
113
- yield(attr_tokens) unless attr_tokens.empty?
72
+ def value_source(value)
73
+ # Ruby 3.1 value omission (`{ foo: }`). An empty value tells AttributeCompiler to
74
+ # fall back to the runtime, which is what resolves it.
75
+ return '' if value.is_a?(Prism::ImplicitNode)
76
+
77
+ value.slice
78
+ end
79
+
80
+ # Prism tags its slices with the encoding it parsed the source under (UTF-8 for a
81
+ # BINARY source), while the rest of the compiled template stays in the source
82
+ # encoding. Mixing the two raises Encoding::CompatibilityError once both sides hold
83
+ # non-ASCII bytes, so bring everything back to the source encoding.
84
+ def in_source_encoding(string, source)
85
+ return string if string.encoding == source.encoding
86
+
87
+ string.dup.force_encoding(source.encoding)
114
88
  end
115
89
  end
116
90
  end
@@ -40,9 +40,7 @@ module Haml
40
40
  when :script, :silent_script
41
41
  @lineno += 1
42
42
  when :tag
43
- [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_hash|
44
- @lineno += attribute_hash.count("\n")
45
- end
43
+ @lineno += node.value[:dynamic_attributes].newline_count
46
44
  @lineno += 1 if node.children.empty? && node.value[:parse]
47
45
  end
48
46
 
@@ -1,18 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
  require 'temple/static_analyzer'
3
+ require 'haml/helpers'
4
+ require 'haml/preserver'
3
5
  require 'haml/ruby_expression'
4
6
  require 'haml/string_splitter'
5
7
 
6
8
  module Haml
7
9
  class Compiler
8
10
  class ScriptCompiler
9
- def self.find_and_preserve(input, tags)
10
- tags = tags.map { |tag| Regexp.escape(tag) }.join('|')
11
- re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
12
- input.to_s.gsub(re) do |s|
13
- s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
14
- "<#{$1}#{$2}>#{Haml::Helpers.preserve($3)}</#{$1}>"
15
- end
11
+ def self.find_and_preserve(input, tags = ::Haml::Preserver::DEFAULT_TAGS)
12
+ ::Haml::Preserver.find_and_preserve(input, tags) { |content| ::Haml::Helpers.preserve(content) }
16
13
  end
17
14
 
18
15
  def initialize(identity, options)
@@ -21,10 +18,6 @@ module Haml
21
18
  end
22
19
 
23
20
  def compile(node, &block)
24
- unless Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
25
- return dynamic_compile(node, &block)
26
- end
27
-
28
21
  no_children = node.children.empty?
29
22
  case
30
23
  when no_children && node.value[:escape_interpolation]
@@ -43,8 +36,13 @@ module Haml
43
36
  # String-interpolated plain text must be compiled with this method
44
37
  # because we have to escape only interpolated values.
45
38
  def compile_interpolated_plain(node)
39
+ compiled = StringSplitter.try_compile(node.value[:text])
40
+ # Ruby that does not parse: let the generated code report it, rather than failing
41
+ # the whole compilation with an error that points at Haml instead of the template.
42
+ return delegate_optimization(node) if compiled.nil?
43
+
46
44
  temple = [:multi]
47
- StringSplitter.compile(node.value[:text]).each do |type, value|
45
+ compiled.each do |type, value|
48
46
  case type
49
47
  when :static
50
48
  temple << [:static, value]
@@ -68,7 +66,7 @@ module Haml
68
66
  if node.value[:escape_html]
69
67
  str = Haml::Util.escape_html(str)
70
68
  elsif node.value[:preserve]
71
- str = ScriptCompiler.find_and_preserve(str, %w(textarea pre code))
69
+ str = ScriptCompiler.find_and_preserve(str)
72
70
  end
73
71
  [:multi, [:static, str], [:newline]]
74
72
  end
@@ -103,7 +101,7 @@ module Haml
103
101
  end
104
102
 
105
103
  def find_and_preserve(code)
106
- %Q[::Haml::Compiler::ScriptCompiler.find_and_preserve(#{code}, %w(textarea pre code))]
104
+ %Q[::Haml::Compiler::ScriptCompiler.find_and_preserve(#{code})]
107
105
  end
108
106
 
109
107
  def escape_html(temple)
@@ -28,10 +28,8 @@ module Haml
28
28
  nil
29
29
  when node.value[:parse]
30
30
  return compile_interpolated_plain(node) if node.value[:escape_interpolation]
31
- if Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
32
- return delegate_optimization(node) if RubyExpression.string_literal?(node.value[:value])
33
- return delegate_optimization(node) if Temple::StaticAnalyzer.static?(node.value[:value])
34
- end
31
+ return delegate_optimization(node) if RubyExpression.string_literal?(node.value[:value])
32
+ return delegate_optimization(node) if Temple::StaticAnalyzer.static?(node.value[:value])
35
33
 
36
34
  var = @identity.generate
37
35
  [:multi,
@@ -55,8 +53,13 @@ module Haml
55
53
 
56
54
  # We should handle interpolation here to escape only interpolated values.
57
55
  def compile_interpolated_plain(node)
56
+ compiled = StringSplitter.try_compile(node.value[:value])
57
+ # Ruby that does not parse: let the generated code report it, rather than failing
58
+ # the whole compilation with an error that points at Haml instead of the template.
59
+ return delegate_optimization(node) if compiled.nil?
60
+
58
61
  temple = [:multi]
59
- StringSplitter.compile(node.value[:value]).each do |type, value|
62
+ compiled.each do |type, value|
60
63
  case type
61
64
  when :static
62
65
  temple << [:static, value]
@@ -36,7 +36,7 @@ module Haml
36
36
  return exps
37
37
  end
38
38
 
39
- strlit_body = String.new
39
+ strlit_body = +''
40
40
  exps.each do |type, arg|
41
41
  case type
42
42
  when :static