diff --git a/Gemfile b/Gemfile index c8b98fee..ce069edf 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ gemspec gem "debug", platform: :mri unless ENV["CI"] == "true" gem "method_source" -gem "unparser" +gem "prism" gem 'sqlite3', "~> 1.4.0", platform: :mri gem 'activerecord-jdbcsqlite3-adapter', '~> 50.0', platform: :jruby diff --git a/docs/debugging.md b/docs/debugging.md index 29d5356b..ab66f6d0 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -2,7 +2,7 @@ **NOTE:** this functionality requires two additional gems to be available in the app: -- [unparser](https://github.com/mbj/unparser) +- [prism](https://github.com/ruby/prism) - [method_source](https://github.com/banister/method_source). We usually describe policy rules using _boolean expressions_ (e.g. `A or (B and C)` where each of `A`, `B` and `C` is a simple boolean expression or predicate method). diff --git a/forspell.dict b/forspell.dict index 5f91c122..a266c15f 100644 --- a/forspell.dict +++ b/forspell.dict @@ -12,7 +12,7 @@ matchers non-inferrable Railsy testability -unparser +prism utils transpiled supersets diff --git a/gemfiles/rails6.gemfile b/gemfiles/rails6.gemfile index 52e19244..e3963c78 100644 --- a/gemfiles/rails6.gemfile +++ b/gemfiles/rails6.gemfile @@ -3,6 +3,6 @@ source "https://rubygems.org" gem "sqlite3", "~> 1.4" gem "rails", "~> 6.0" gem "method_source" -gem "unparser" +gem "prism" gemspec path: ".." diff --git a/gemfiles/rails7.gemfile b/gemfiles/rails7.gemfile index cf47bb9b..ef0a7a07 100644 --- a/gemfiles/rails7.gemfile +++ b/gemfiles/rails7.gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" gem "sqlite3", "~> 1.4" gem "rails", "~> 7.0.0" gem "method_source" -gem "unparser" +gem "prism" if ENV["COVERAGE"] == "true" gem "simplecov" diff --git a/gemfiles/rails71.gemfile b/gemfiles/rails71.gemfile index 13a867c7..49303183 100644 --- a/gemfiles/rails71.gemfile +++ b/gemfiles/rails71.gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" gem "sqlite3", "~> 1.4" gem "rails", "~> 7.1.0" gem "method_source" -gem "unparser" +gem "prism" if ENV["COVERAGE"] == "true" gem "simplecov" diff --git a/lib/action_policy/policy/core.rb b/lib/action_policy/policy/core.rb index d4741acd..e4a2ea77 100644 --- a/lib/action_policy/policy/core.rb +++ b/lib/action_policy/policy/core.rb @@ -147,7 +147,7 @@ def resolve_rule(activity) end # Return annotated source code for the rule - # NOTE: require "method_source" and "unparser" gems to be installed. + # NOTE: require "method_source" and "prism" gems to be installed. # Otherwise returns empty string. def inspect_rule(rule) = PrettyPrint.print_method(self, rule) diff --git a/lib/action_policy/utils/pretty_print.rb b/lib/action_policy/utils/pretty_print.rb index a66dd11c..92857e23 100644 --- a/lib/action_policy/utils/pretty_print.rb +++ b/lib/action_policy/utils/pretty_print.rb @@ -7,8 +7,7 @@ # Ignore parse warnings when patch # Ruby version mismatches $VERBOSE = nil - require "parser/current" - require "unparser" + require "prism" rescue LoadError # do nothing ensure @@ -42,7 +41,7 @@ module PrettyPrint FALSE = "\e[31mfalse\e[0m" class Visitor - attr_reader :lines, :object + attr_reader :lines, :object, :source attr_accessor :indent def initialize(object) @@ -52,6 +51,8 @@ def initialize(object) def collect(ast) @lines = [] @indent = 0 + @source = ast.source.source + ast = ast.value.child_nodes[0].child_nodes[0].body visit_node(ast) @@ -67,7 +68,7 @@ def visit_node(ast) end def expression_with_result(sexp) - expression = Unparser.unparse(sexp) + expression = source[sexp.location.start_offset...sexp.location.end_offset] "#{expression} #=> #{PrettyPrint.colorize(eval_exp(expression))}" end @@ -78,36 +79,33 @@ def eval_exp(exp) "Failed: #{e.message}" end - def visit_and(ast) - visit_node(ast.children[0]) + def visit_and_node(ast) + visit_node(ast.left) lines << indented("AND") - visit_node(ast.children[1]) + visit_node(ast.right) end - def visit_or(ast) - visit_node(ast.children[0]) + def visit_or_node(ast) + visit_node(ast.left) lines << indented("OR") - visit_node(ast.children[1]) + visit_node(ast.right) end - def visit_begin(ast) - # Parens - if ast.children.size == 1 - lines << indented("(") - self.indent += 2 - visit_node(ast.children[0]) + def visit_statements_node(ast) + ast.child_nodes.each do |node| + visit_node(node) + # restore indent after each expression self.indent -= 2 - lines << indented(")") - else - # Multiple expressions - ast.children.each do |node| - visit_node(node) - # restore indent after each expression - self.indent -= 2 - end end end + def visit_parentheses_node(ast) + lines << indented("(") + self.indent += 2 + visit_node(ast.child_nodes[0]) + lines << indented(")") + end + def visit_missing(ast) lines << indented(expression_with_result(ast)) end @@ -128,15 +126,13 @@ def ignore_exp?(exp) class << self attr_accessor :ignore_expressions - if defined?(::Unparser) && defined?(::MethodSource) + if defined?(::Prism) && defined?(::MethodSource) def available?() = true def print_method(object, method_name) - ast = object.method(method_name).source.then(&Unparser.:parse) - # outer node is a method definition itself - body = ast.children[2] + ast = Prism.parse(object.method(method_name).source) - Visitor.new(object).collect(body) + Visitor.new(object).collect(ast) end else def available?() = false