Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug Report] undefined method `pair_label' for #<Packwerk::Parsers::Ruby::TolerateInvalidUtf8Builder #424

Closed
lucasklaassen opened this issue Jan 23, 2025 · 1 comment
Labels
bug Something isn't working

Comments

@lucasklaassen
Copy link

Description
We’re encountering a NoMethodError when Packwerk parses code that uses Ruby’s newer keyword argument shorthand (e.g., param_id: without explicitly specifying param_id: param_id). The error consistently appears when running packwerk check.

To Reproduce

Use Ruby 3.1 (or higher).
In a file (e.g., app/services/create_a_thing.rb), write:

class CreateAThing
  def call(param_id)
    # Using Ruby’s shorthand keyword argument syntax
    Record.find_by(param_id:, active: true)
  end
end

Run bundle exec packwerk check.

Packwerk fails with a stack trace ending in:

undefined method `pair_label' for #<Packwerk::Parsers::Ruby::TolerateInvalidUtf8Builder:0x0000...>

Expected Behaviour
Packwerk should parse this syntax successfully (it’s valid Ruby 3 code) and continue checking for violations without raising an error.

Version Information

  • Packwerk: 3.2.2
  • Prism: 1.3.0
  • Ruby: 3.1.4

Additional Context

  • The error is linked to code that uses the “shorthand” keyword argument syntax introduced in Ruby 3 (e.g., param_id: in place of param_id: param_id).
  • If we rewrite the shorthand argument to the more explicit Record.find_by(param_id: param_id, active: true), Packwerk no longer fails.
  • Our investigation suggests Prism expects a builder method pair_label, but Packwerk’s custom TolerateInvalidUtf8Builder does not implement it.
  • We’ve tried upgrading Prism (currently on 1.3.0), but the error persists.

Stacktrace:

 builder.pair_label([key.unescaped, srange(key.location)])
                     ^^^^^^^^^^^ /app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser/compiler.rb:138:in `visit_assoc_node'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/node.rb:1195:in `accept'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `block in visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `map'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser/compiler.rb:1155:in `visit_keyword_hash_node'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/node.rb:11345:in `accept'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `block in visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `map'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser/compiler.rb:324:in `visit_call_node'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/node.rb:2613:in `accept'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `block in visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `map'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser/compiler.rb:1667:in `visit_statements_node'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/node.rb:16776:in `accept'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser/compiler.rb:656:in `visit_def_node'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/node.rb:5909:in `accept'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `block in visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `map'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser/compiler.rb:1667:in `visit_statements_node'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/node.rb:16776:in `accept'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser/compiler.rb:441:in `visit_class_node'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/node.rb:3831:in `accept'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `block in visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `map'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:35:in `visit_all'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser/compiler.rb:1667:in `visit_statements_node'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/node.rb:16776:in `accept'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/compiler.rb:30:in `visit'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser/compiler.rb:1475:in `visit_program_node'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/node.rb:14883:in `accept'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser.rb:264:in `build_ast'
/app/vendor/bundle/ruby/3.1.0/gems/prism-1.3.0/lib/prism/translation/parser.rb:56:in `parse'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/parsers/ruby.rb:51:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/file_processor.rb:87:in `block in parse_into_ast'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/file_processor.rb:86:in `open'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/file_processor.rb:86:in `parse_into_ast'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/file_processor.rb:47:in `block in call'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/cache.rb:78:in `with_cache'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/file_processor.rb:46:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/run_context.rb:80:in `process_file'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/parse_run.rb:57:in `block in process_file_proc'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:515:in `call_with_index'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:485:in `process_incoming_jobs'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:465:in `block in worker'
/app/vendor/bundle/ruby/3.1.0/gems/activesupport-6.1.5/lib/active_support/fork_tracker.rb:10:in `block in fork'
/app/vendor/bundle/ruby/3.1.0/gems/activesupport-6.1.5/lib/active_support/fork_tracker.rb:8:in `fork'
/app/vendor/bundle/ruby/3.1.0/gems/activesupport-6.1.5/lib/active_support/fork_tracker.rb:8:in `fork'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:456:in `worker'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:447:in `block in create_workers'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:446:in `each'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:446:in `each_with_index'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:446:in `create_workers'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:386:in `work_in_processes'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:289:in `map'
/app/vendor/bundle/ruby/3.1.0/gems/parallel-1.21.0/lib/parallel.rb:302:in `flat_map'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/parse_run.rb:38:in `find_offenses'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/commands/check_command.rb:27:in `block in run'
/home/dev/.asdf/installs/ruby/3.1.4/lib/ruby/3.1.0/benchmark.rb:311:in `realtime'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/formatters/progress_formatter.rb:29:in `started_inspection'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/commands/check_command.rb:26:in `run'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/cli.rb:58:in `execute_command'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/lib/packwerk/cli.rb:41:in `run'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `bind_call'
/app/vendor/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.11778/lib/types/private/methods/_methods.rb:279:in `block in _on_method_added'
/app/vendor/bundle/ruby/3.1.0/gems/packwerk-3.2.2/exe/packwerk:16:in `<top (required)>'
bin/packwerk:27:in `load'
bin/packwerk:27:in `<main>'"
@lucasklaassen lucasklaassen added the bug Something isn't working label Jan 23, 2025
@lucasklaassen
Copy link
Author

The fix here is to ensure you're using at least this version of the parser gem:

gem 'parser', '~> 3.3.1.0'

Seems as though the pair_label method wasn't implemented in previous versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant