klenod-build 0.0.26 → 0.0.27

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.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "syntax_tree"
3
+ require "prism"
4
4
  require "ripper"
5
5
 
6
6
  require "klenod/runtime/source_map"
@@ -681,16 +681,15 @@ module Klenod
681
681
  builder.ruby_parse_error(source, line_no: node.line, context: "Could not parse Haml dynamic attributes")
682
682
  end
683
683
 
684
- hash.node.assocs.each do |assoc|
685
- if assoc.is_a?(SyntaxTree::AssocSplat)
686
- splat_source = hash.source[assoc.value.location.start_char...assoc.value.location.end_char]
684
+ hash.node.elements.each do |assoc|
685
+ if assoc.is_a?(Prism::AssocSplatNode)
687
686
  props[ATTRIBUTE_SPLATS_KEY] ||= []
688
- props[ATTRIBUTE_SPLATS_KEY] << builder.expression(splat_source)
687
+ props[ATTRIBUTE_SPLATS_KEY] << builder.expression(assoc.value.slice)
689
688
  next
690
689
  end
691
690
 
692
691
  key = attribute_key(assoc.key, builder: builder)
693
- value = event_handler_value(key, assoc.value, parenthesized: parenthesized, builder: builder) || attribute_value(assoc, builder: builder)
692
+ value = event_handler_value(key, explicit_attribute_value(assoc), parenthesized: parenthesized, builder: builder) || attribute_value(assoc, builder: builder)
694
693
  dynamic[key] = value
695
694
  props[key] = value
696
695
  end
@@ -799,8 +798,8 @@ module Klenod
799
798
  measure_compile(:haml_compile_object_ref_attributes) do
800
799
  expression = builder.expression(source, line_no: node.line)
801
800
  key =
802
- if expression.node.is_a?(SyntaxTree::ArrayLiteral) && expression.node.contents&.parts&.length == 1
803
- builder.fragment(expression.node.contents.parts.fetch(0))
801
+ if expression.node.is_a?(Prism::ArrayNode) && expression.node.elements.length == 1
802
+ builder.fragment(expression.node.elements.fetch(0))
804
803
  else
805
804
  expression
806
805
  end
@@ -810,7 +809,7 @@ module Klenod
810
809
  end
811
810
 
812
811
  def attribute_value(assoc, builder:)
813
- value = assoc.value
812
+ value = explicit_attribute_value(assoc)
814
813
  return builder.fragment(value) if value
815
814
 
816
815
  key = omitted_attribute_value_name(assoc.key)
@@ -819,6 +818,12 @@ module Klenod
819
818
  builder.ruby_parse_error(source, line_no: assoc.key.location&.start_line, context: "Could not parse Haml dynamic attributes")
820
819
  end
821
820
 
821
+ # `{title:}` omits the value, which Prism represents as an implicit
822
+ # node that slices back to the `title:` label.
823
+ def explicit_attribute_value(assoc)
824
+ assoc.value unless assoc.value.is_a?(Prism::ImplicitNode)
825
+ end
826
+
822
827
  def event_handler_value(key, value, parenthesized:, builder:)
823
828
  normalized_key = key.to_s.tr("-", "_")
824
829
  return unless normalized_key.match?(/\Aon(?:[a-z]|_[a-z])/)
@@ -836,36 +841,23 @@ module Klenod
836
841
 
837
842
  def omitted_attribute_value_name(node)
838
843
  case node
839
- when SyntaxTree::Label
840
- name = node.value.delete_suffix(":")
844
+ when Prism::SymbolNode
845
+ name = node.unescaped
841
846
  name if name.match?(/\A[a-zA-Z_]\w*\z/)
842
847
  end
843
848
  end
844
849
 
850
+ # Static keys (`title:`, `"data-id":`, `:title =>`, `"title" =>`) use
851
+ # their value. Interpolated keys keep their source.
845
852
  def attribute_key(node, builder:)
846
853
  case node
847
- when SyntaxTree::Label
848
- node.value.delete_suffix(":").to_sym
849
- when SyntaxTree::DynaSymbol
850
- static_dyna_symbol_value(node)&.to_sym || builder.fragment(node).source.to_sym
851
- when SyntaxTree::StringLiteral
852
- node.parts.map(&:value).join.to_sym
854
+ when Prism::SymbolNode, Prism::StringNode
855
+ node.unescaped.to_sym
853
856
  else
854
857
  builder.fragment(node).source.to_sym
855
858
  end
856
859
  end
857
860
 
858
- def static_dyna_symbol_value(node)
859
- values =
860
- node.parts.map do |part|
861
- return nil unless part.is_a?(SyntaxTree::TStringContent)
862
-
863
- part.value
864
- end
865
-
866
- values.join
867
- end
868
-
869
861
  def source_mark(node, builder:)
870
862
  builder.source_mark(node.line, nil)
871
863
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "syntax_tree"
3
+ require "prism"
4
4
 
5
5
  require_relative "../plugin"
6
6
  require_relative "../dependency"
@@ -301,13 +301,13 @@ module Klenod
301
301
  def validate_variable_receiver(kind, source)
302
302
  raise ArgumentError, "variables[#{kind.inspect}] must be a Ruby expression String" unless source.is_a?(String)
303
303
 
304
- parsed = SyntaxTree.parse(source)&.statements&.body
305
- raise ArgumentError, "variables[#{kind.inspect}] must be a Ruby expression" unless parsed&.length == 1
306
- SyntaxTree.parse("(#{source})[:__klenod_variable__]")
304
+ parsed = Prism.parse(source)
305
+ expression = parsed.success? && parsed.value.statements.body.length == 1
306
+ unless expression && Prism.parse_success?("(#{source})[:__klenod_variable__]")
307
+ raise ArgumentError, "variables[#{kind.inspect}] must be a Ruby expression"
308
+ end
307
309
 
308
310
  source
309
- rescue SyntaxTree::Parser::ParseError
310
- raise ArgumentError, "variables[#{kind.inspect}] must be a Ruby expression"
311
311
  end
312
312
 
313
313
  def validate_i18n_options(i18n)
@@ -23,15 +23,7 @@ module Klenod
23
23
  private
24
24
 
25
25
  def location(error)
26
- return prism_location(error) if error.is_a?(Prism::ParseResult)
27
- return nil unless error.respond_to?(:lineno)
28
-
29
- Location.new(
30
- line: error.lineno,
31
- # SyntaxTree reports a zero-based column.
32
- column: error.column && error.column + 1,
33
- detail: error.message
34
- )
26
+ prism_location(error) if error.is_a?(Prism::ParseResult)
35
27
  end
36
28
 
37
29
  # Prism reports each failure as data rather than a message to scrape,
@@ -56,18 +48,14 @@ module Klenod
56
48
  program = parse!(module_id, code)
57
49
 
58
50
  result =
59
- begin
60
- RubyImportRewriter
61
- .new(
62
- module_id: module_id,
63
- kind: :ruby_import,
64
- source_dir: context.source_dir,
65
- profiler: context.profiler
66
- )
67
- .rewrite(code)
68
- rescue SyntaxTree::Parser::ParseError => error
69
- raise ParseError.new(error, source: code, module_id: module_id)
70
- end
51
+ RubyImportRewriter
52
+ .new(
53
+ module_id: module_id,
54
+ kind: :ruby_import,
55
+ source_dir: context.source_dir,
56
+ profiler: context.profiler
57
+ )
58
+ .rewrite(code)
71
59
  TransformResult.new(
72
60
  result.code,
73
61
  result.dependencies,
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "prism"
3
4
  require "ripper"
4
- require "syntax_tree"
5
5
 
6
6
  require_relative "dependency"
7
7
  require_relative "errors"
@@ -21,6 +21,8 @@ module Klenod
21
21
  end
22
22
  end
23
23
  Result = Data.define(:code, :dependencies, :watched_patterns)
24
+ # Character-based, so it indexes the source String directly.
25
+ CallLocation = Data.define(:start_line, :start_column, :start_char, :end_char)
24
26
 
25
27
  IMPORT_METHODS = {
26
28
  "import" => {
@@ -66,13 +68,20 @@ module Klenod
66
68
  fast_result = rewrite_literal_import_calls(code)
67
69
  return fast_result if fast_result
68
70
 
69
- ast =
70
- begin
71
- measure(:ruby_import_parse) { SyntaxTree.parse(code) }
72
- rescue SyntaxTree::Parser::ParseError => error
73
- raise ParseError.new(error.message, line: error.lineno + @source_line_offset, column: error.column + 1 + @source_column_offset)
74
- end
75
- calls = measure(:ruby_import_scan) { import_calls(ast) }
71
+ # Haml `:ruby` filters are fragments, so accept top-level `yield`,
72
+ # `next`, and `break` like the rest of the Haml transform.
73
+ result = measure(:ruby_import_parse) { Prism.parse(code, partial_script: true) }
74
+ unless result.success?
75
+ # Prism first reports an unclosed `{` or `(` at the line that opens
76
+ # it. The unexpected token that broke it is the line to show.
77
+ error = result.errors.reject { it.type.end_with?("_term") }.min_by { it.location.start_offset } || result.errors.first
78
+ raise ParseError.new(
79
+ error.message,
80
+ line: error.location.start_line + @source_line_offset,
81
+ column: error.location.start_character_column + 1 + @source_column_offset
82
+ )
83
+ end
84
+ calls = measure(:ruby_import_scan) { import_calls(result.value) }
76
85
  expanded = expand_import_calls(calls)
77
86
 
78
87
  rewritten = measure(:ruby_import_rewrite_source) { rewrite_import_calls(code, expanded) }
@@ -180,121 +189,127 @@ module Klenod
180
189
  end
181
190
 
182
191
  def import_calls(node)
183
- calls = []
184
- walk(node) do |child|
185
- if child.instance_of?(::SyntaxTree::CallNode)
186
- next unless child.instance_variable_get(:@receiver).nil?
187
- next unless IMPORT_METHODS.key?(child.instance_variable_get(:@message)&.value)
192
+ visitor = ImportCallVisitor.new
193
+ node.accept(visitor)
194
+ visitor.calls.map { |call| build_import_call(call) }
195
+ end
188
196
 
189
- calls << build_import_call(child)
190
- elsif child.instance_of?(::SyntaxTree::Command)
191
- next unless IMPORT_METHODS.key?(child.instance_variable_get(:@message)&.value)
197
+ class ImportCallVisitor < Prism::Visitor
198
+ attr_reader :calls
192
199
 
193
- calls << build_import_command(child)
194
- end
200
+ def initialize
201
+ @calls = []
202
+ super
195
203
  end
196
- calls
197
- end
198
204
 
199
- def build_import_call(node)
200
- arguments = node.instance_variable_get(:@arguments)
201
- parts = arguments&.instance_variable_get(:@arguments)&.instance_variable_get(:@parts) || []
202
- build_import_from_parts(node, parts)
203
- end
205
+ def visit_call_node(node)
206
+ @calls << node if import_call?(node)
207
+ super
208
+ end
209
+
210
+ private
204
211
 
205
- def build_import_command(node)
206
- arguments = node.instance_variable_get(:@arguments)
207
- parts = arguments&.instance_variable_get(:@parts) || []
208
- build_import_from_parts(node, parts)
212
+ # A bare `import` without arguments or parentheses is an ordinary
213
+ # method call or local variable, not an import.
214
+ def import_call?(node)
215
+ node.receiver.nil? &&
216
+ IMPORT_METHODS.key?(node.name.to_s) &&
217
+ !(node.arguments.nil? && node.opening_loc.nil?)
218
+ end
209
219
  end
220
+ private_constant :ImportCallVisitor
210
221
 
211
- def build_import_from_parts(node, parts)
212
- return build_import_glob_from_parts(node, parts) if node.instance_variable_get(:@message).value == "import_glob"
222
+ def build_import_call(node)
223
+ arguments = node.arguments&.arguments || []
224
+ return build_import_glob(node, arguments) if node.name == :import_glob
213
225
 
214
- first = unwrap_paren(parts.first)
215
- string_parts = first&.instance_variable_get(:@parts)
216
- literal_specifier =
217
- first&.class&.name == "SyntaxTree::StringLiteral" &&
218
- string_parts&.length == 1 &&
219
- string_parts.first.instance_of?(::SyntaxTree::TStringContent)
220
- import_name = (parts.length == 2) ? constant_symbol_value(parts.fetch(1)) : nil
221
- literal = literal_specifier && (parts.length == 1 || !import_name.nil?)
226
+ specifier = string_literal_value(arguments.first)
227
+ import_name = (arguments.length == 2) ? constant_symbol_value(arguments.fetch(1)) : nil
228
+ literal = !specifier.nil? && (arguments.length == 1 || !import_name.nil?)
222
229
 
223
230
  ImportCall.new(
224
- literal ? string_parts.first.value : nil,
225
- node.instance_variable_get(:@location),
231
+ literal ? specifier : nil,
232
+ call_location(node),
226
233
  !literal,
227
- node.instance_variable_get(:@message).value,
234
+ node.name.to_s,
228
235
  nil,
229
236
  literal ? import_name : nil
230
237
  )
231
238
  end
232
239
 
240
+ # A Prism call's location includes an attached block, so the rewritten
241
+ # range ends at the closing parenthesis or the last argument.
242
+ def call_location(node)
243
+ last = node.closing_loc || node.arguments&.location || node.message_loc
244
+
245
+ CallLocation.new(
246
+ node.location.start_line,
247
+ node.location.start_character_column,
248
+ node.location.start_character_offset,
249
+ last.end_character_offset
250
+ )
251
+ end
252
+
253
+ # A plain string literal without interpolation, such as `"./Card.haml"`.
254
+ def string_literal_value(node)
255
+ node = unwrap_parentheses(node)
256
+ return unless node.is_a?(Prism::StringNode) && !node.heredoc? && !node.unescaped.empty?
257
+
258
+ node.unescaped
259
+ end
260
+
233
261
  # The optional second argument of `import`: a symbol literal spelled like
234
262
  # a constant, such as `:Bar`. Anything else (`:bar`, `:"Bar"`, a string,
235
263
  # a variable) is reported as a dynamic import.
236
264
  def constant_symbol_value(node)
237
- return unless node.instance_of?(::SyntaxTree::SymbolLiteral)
265
+ return unless node.is_a?(Prism::SymbolNode) && node.opening_loc&.slice == ":"
238
266
 
239
- value = node.instance_variable_get(:@value)
240
- value.instance_of?(::SyntaxTree::Const) ? value.value.to_sym : nil
267
+ value = node.unescaped
268
+ value.match?(/\A[A-Z]\w*\z/) ? value.to_sym : nil
241
269
  end
242
270
 
243
- def build_import_glob_from_parts(node, parts)
244
- first = unwrap_paren(parts.first)
245
- string_parts = first&.instance_variable_get(:@parts)
246
- literal =
247
- first&.class&.name == "SyntaxTree::StringLiteral" &&
248
- string_parts&.length == 1 &&
249
- string_parts.first.instance_of?(::SyntaxTree::TStringContent)
271
+ def build_import_glob(node, arguments)
272
+ specifier = string_literal_value(arguments.first)
273
+ literal = !specifier.nil?
250
274
 
251
275
  eager = true
252
276
  valid_options = true
253
- if parts.length == 2
254
- eager = eager_option_value(parts.fetch(1))
277
+ if arguments.length == 2
278
+ eager = eager_option_value(arguments.fetch(1))
255
279
  valid_options = !eager.nil?
256
- elsif parts.length != 1
280
+ elsif arguments.length != 1
257
281
  valid_options = false
258
282
  end
259
283
 
260
284
  ImportCall.new(
261
- (literal && valid_options) ? string_parts.first.value : nil,
262
- node.instance_variable_get(:@location),
285
+ (literal && valid_options) ? specifier : nil,
286
+ call_location(node),
263
287
  !literal || !valid_options,
264
- node.instance_variable_get(:@message).value,
288
+ node.name.to_s,
265
289
  eager,
266
290
  nil
267
291
  )
268
292
  end
269
293
 
270
294
  def eager_option_value(node)
271
- return unless node.instance_of?(::SyntaxTree::BareAssocHash)
272
-
273
- assocs = node.instance_variable_get(:@assocs)
274
- return unless assocs.length == 1
295
+ return unless node.is_a?(Prism::KeywordHashNode) && node.elements.length == 1
275
296
 
276
- assoc = assocs.fetch(0)
277
- key = assoc.instance_variable_get(:@key)
278
- return unless key.instance_of?(::SyntaxTree::Label) && key.value == "eager:"
297
+ assoc = node.elements.fetch(0)
298
+ return unless assoc.is_a?(Prism::AssocNode)
279
299
 
280
- bool_value(assoc.instance_variable_get(:@value))
281
- end
282
-
283
- def bool_value(node)
284
- return unless node.instance_of?(::SyntaxTree::VarRef)
300
+ key = assoc.key
301
+ return unless key.is_a?(Prism::SymbolNode) && key.opening_loc.nil? && key.unescaped == "eager"
285
302
 
286
- value = node.instance_variable_get(:@value)
287
- return true if value.respond_to?(:value) && value.value == "true"
288
- return false if value.respond_to?(:value) && value.value == "false"
289
-
290
- nil
303
+ case assoc.value
304
+ when Prism::TrueNode then true
305
+ when Prism::FalseNode then false
306
+ end
291
307
  end
292
308
 
293
- def unwrap_paren(node)
294
- return node unless node.instance_of?(::SyntaxTree::Paren)
309
+ def unwrap_parentheses(node)
310
+ return node unless node.is_a?(Prism::ParenthesesNode)
295
311
 
296
- statements = node.instance_variable_get(:@contents)
297
- body = statements&.instance_variable_get(:@body) || []
312
+ body = node.body.is_a?(Prism::StatementsNode) ? node.body.body : []
298
313
  (body.length == 1) ? body.first : node
299
314
  end
300
315
 
@@ -455,21 +470,6 @@ module Klenod
455
470
  rewritten[location.start_char...location.end_char] = import.replacement_source
456
471
  end
457
472
  end
458
-
459
- def walk(value, &block)
460
- yield value
461
-
462
- case value
463
- when Array
464
- value.each { |item| walk(item, &block) }
465
- else
466
- return unless value.respond_to?(:instance_variables)
467
-
468
- value.instance_variables.each do |ivar|
469
- walk(value.instance_variable_get(ivar), &block)
470
- end
471
- end
472
- end
473
473
  end
474
474
  end
475
475
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Klenod
4
4
  module Build
5
- VERSION = "0.0.26"
5
+ VERSION = "0.0.27"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klenod-build
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.26
4
+ version: 0.0.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrés Alin
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.0.26
18
+ version: 0.0.27
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.0.26
25
+ version: 0.0.27
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: async
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -85,6 +85,20 @@ dependencies:
85
85
  - - "~>"
86
86
  - !ruby/object:Gem::Version
87
87
  version: '0.8'
88
+ - !ruby/object:Gem::Dependency
89
+ name: haml
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '7.2'
95
+ type: :runtime
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '7.2'
88
102
  - !ruby/object:Gem::Dependency
89
103
  name: image_size
90
104
  requirement: !ruby/object:Gem::Requirement
@@ -142,75 +156,61 @@ dependencies:
142
156
  - !ruby/object:Gem::Version
143
157
  version: '3.10'
144
158
  - !ruby/object:Gem::Dependency
145
- name: protocol-url
146
- requirement: !ruby/object:Gem::Requirement
147
- requirements:
148
- - - "~>"
149
- - !ruby/object:Gem::Version
150
- version: '0.4'
151
- type: :runtime
152
- prerelease: false
153
- version_requirements: !ruby/object:Gem::Requirement
154
- requirements:
155
- - - "~>"
156
- - !ruby/object:Gem::Version
157
- version: '0.4'
158
- - !ruby/object:Gem::Dependency
159
- name: rmagick
159
+ name: prism
160
160
  requirement: !ruby/object:Gem::Requirement
161
161
  requirements:
162
162
  - - "~>"
163
163
  - !ruby/object:Gem::Version
164
- version: '7.0'
164
+ version: '1.9'
165
165
  type: :runtime
166
166
  prerelease: false
167
167
  version_requirements: !ruby/object:Gem::Requirement
168
168
  requirements:
169
169
  - - "~>"
170
170
  - !ruby/object:Gem::Version
171
- version: '7.0'
171
+ version: '1.9'
172
172
  - !ruby/object:Gem::Dependency
173
- name: samovar
173
+ name: protocol-url
174
174
  requirement: !ruby/object:Gem::Requirement
175
175
  requirements:
176
176
  - - "~>"
177
177
  - !ruby/object:Gem::Version
178
- version: '2.5'
178
+ version: '0.4'
179
179
  type: :runtime
180
180
  prerelease: false
181
181
  version_requirements: !ruby/object:Gem::Requirement
182
182
  requirements:
183
183
  - - "~>"
184
184
  - !ruby/object:Gem::Version
185
- version: '2.5'
185
+ version: '0.4'
186
186
  - !ruby/object:Gem::Dependency
187
- name: syntax_tree
187
+ name: rmagick
188
188
  requirement: !ruby/object:Gem::Requirement
189
189
  requirements:
190
190
  - - "~>"
191
191
  - !ruby/object:Gem::Version
192
- version: '6.3'
192
+ version: '7.0'
193
193
  type: :runtime
194
194
  prerelease: false
195
195
  version_requirements: !ruby/object:Gem::Requirement
196
196
  requirements:
197
197
  - - "~>"
198
198
  - !ruby/object:Gem::Version
199
- version: '6.3'
199
+ version: '7.0'
200
200
  - !ruby/object:Gem::Dependency
201
- name: syntax_tree-haml
201
+ name: samovar
202
202
  requirement: !ruby/object:Gem::Requirement
203
203
  requirements:
204
204
  - - "~>"
205
205
  - !ruby/object:Gem::Version
206
- version: '4.0'
206
+ version: '2.5'
207
207
  type: :runtime
208
208
  prerelease: false
209
209
  version_requirements: !ruby/object:Gem::Requirement
210
210
  requirements:
211
211
  - - "~>"
212
212
  - !ruby/object:Gem::Version
213
- version: '4.0'
213
+ version: '2.5'
214
214
  - !ruby/object:Gem::Dependency
215
215
  name: toml-rb
216
216
  requirement: !ruby/object:Gem::Requirement