From a78ccf69e5c7885185cc223b080efc9edcf11d60 Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Sun, 21 May 2023 22:18:16 +0300 Subject: [PATCH 1/6] Fix code example `RuboCop::ProcessedSource` is from `rubocop`. --- docs/modules/ROOT/pages/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 6919b48ab..506a4246a 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -78,7 +78,7 @@ class MyRule < Parser::AST::Processor end end -source = RuboCop::ProcessedSource.new(code, 2.7) +source = RuboCop::AST::ProcessedSource.new(code, 2.7) rule = MyRule.new source.ast.each_node { |n| rule.process(n) } ---- From c86ee8e2137704ad9b1abe1315d30579fcc9e33d Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Sun, 21 May 2023 22:20:17 +0300 Subject: [PATCH 2/6] Add basic introduction to node pattern doc --- docs/modules/ROOT/pages/node_pattern.adoc | 78 +++++++++++++++++++++-- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/docs/modules/ROOT/pages/node_pattern.adoc b/docs/modules/ROOT/pages/node_pattern.adoc index 7aa365319..55c60418d 100644 --- a/docs/modules/ROOT/pages/node_pattern.adoc +++ b/docs/modules/ROOT/pages/node_pattern.adoc @@ -47,20 +47,84 @@ def on_send(node) end ---- -== `(` and `)` Navigate deeply with Parens +== Ruby Abstract Syntax Tree (AST) -Parens delimits navigation inside node and its children. +Parser translates Ruby source code to a tree structure represented in text. +A simple integer literal like `1` is represented by `(int 1)` in the AST. +A method call with two integer literals: -A simple integer like `1` is represented by `(int 1)` in the AST. +[source,ruby] +---- +foo(1, 2) +---- + +is represented with: + +[source] +---- +(send nil :foo + (int 1) + (int 2) +) +---- + +Every node is represented with a sequence. +The first element is the node type. +Other elements are child nodes. They are optionally present and depend on the node type. +E.g.: + +* `nil` is just `(nil)` +* `1` is `(int 1)` +* `[1]` is `(array (int 1))` +* `[1, 2]` is `(array (int 1) (int 2))` +* `foo` is `(send nil :foo)` +* `foo(1)` is `(send nil :foo (int 1))` + +=== Getting the AST representation + +==== From the command-line with `ruby-parse` [source,sh] ---- -$ ruby-parse -e '1' -(int 1) +$ ruby-parse --legacy -e 'foo(1)' +(send nil :foo + (int 1)) +---- + +NOTE: Use the `--legacy` `ruby-parse` flag to get https://github.com/whitequark/parser/#usage[the same AST that RuboCop AST returns]. +There are several differences, e.g. without `--legacy`, `foo(a: 1)` would return `kwargs`, and with `--legacy` it returns `hash`. + +==== From REPL + +[source,ruby] +---- +> puts RuboCop::AST::ProcessedSource.new('foo(1)', RUBY_VERSION.to_f).ast.to_s +(send nil :foo + (int 1)) ---- -* `int` will match exactly the node, looking only the node type. -* `(int 1)` will match precisely the node +== Basic Node Pattern Structure + +The simplest Node Pattern would match just the node type. +E.g. the `int` node pattern would match the `(int 1)` AST (literal `1` in Ruby code). +More sophisticated node patterns match more than one child. + +== `(` and `)` to Match Elements + +Several matchers surrounded by parentheses would match a node with elements each matching a corresponding matcher, order-dependently. +Ruby code with an array with two integer literals, `[1, 2]` represented in AST as `(array (int 1) (int 2))` could be matched with `(array int int)` node pattern. + +For a literal integer, e.g. `1` Ruby code represented by `(int 1)` in AST: + +* `int` node pattern will match exactly the node, looking only the node type +* `(int 1)` node pattern will match precisely the node +* `(int 2)` node pattern will not match + +== `(` and `)` for Nested Matching + +Ruby code with a method call with two integer literals as arguments, `foo(1, 2)` represented in AST as `(send nil :foo (int 1) (int 2))` could be matched with `(send nil? :foo int int)` node pattern. +To match just those method calls where the first argument is a literal `1`, use `(send nil? :foo (int 1) int)`. +Any child that is a node can be a target for nested matching. == `_` for any single node From e6afef4a055126694dc8b577625cf7882fe8e4af Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Sun, 21 May 2023 22:26:19 +0300 Subject: [PATCH 3/6] Add a ref to a concept described later in the doc --- docs/modules/ROOT/pages/node_pattern.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/modules/ROOT/pages/node_pattern.adoc b/docs/modules/ROOT/pages/node_pattern.adoc index 55c60418d..2a843852e 100644 --- a/docs/modules/ROOT/pages/node_pattern.adoc +++ b/docs/modules/ROOT/pages/node_pattern.adoc @@ -271,6 +271,8 @@ Imagine you want to check if the number is `odd?` and also positive numbers: `(int [odd? positive?])` - is an int and the value should be odd and positive. +NOTE: Refer to <> to see how `odd?` works. + == `$` for captures You can capture elements or nodes along with your search, prefixing the expression From 01a1ea8354878ae38f9bf48efe889e68ab6f801b Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Sun, 21 May 2023 22:46:55 +0300 Subject: [PATCH 4/6] Add doc for negation syntax --- docs/modules/ROOT/pages/node_pattern.adoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/modules/ROOT/pages/node_pattern.adoc b/docs/modules/ROOT/pages/node_pattern.adoc index 2a843852e..a7d03a2c3 100644 --- a/docs/modules/ROOT/pages/node_pattern.adoc +++ b/docs/modules/ROOT/pages/node_pattern.adoc @@ -273,6 +273,16 @@ Imagine you want to check if the number is `odd?` and also positive numbers: NOTE: Refer to <> to see how `odd?` works. +== `!` for Negation + +Node pattern `(send nil? :sum !int _)` would match a `sum` call where the first argument is *not* a literal integer. +E.g.: + +* it will match `sum(2.0, 3)`, as the first argument is of a `float` type +* it will not match `sum(2, 3)`, as the first argument is of an `int` type + +NOTE: Negation operator works with other node pattern syntax elements, `{}`, `[]`, `()`, `$`, but only with those that target a single element. E.g. `$!(int 1)`, `!{false nil}`, `![#positive? #even?]` will work, while `!{int int | sym}`, `!{int int | sym sym}`, and any use of `<>` won't. + == `$` for captures You can capture elements or nodes along with your search, prefixing the expression From 02d78f011177018f659ed07c08523acdfa78026d Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Sun, 21 May 2023 22:54:22 +0300 Subject: [PATCH 5/6] Fix `ruby-parse` example Without `--legacy`, it would return `kwargs`, not `hash`: (send (const nil :Comment) :new (kwargs (pair (sym :user) (send nil :current_user)))) --- docs/modules/ROOT/pages/node_pattern.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/node_pattern.adoc b/docs/modules/ROOT/pages/node_pattern.adoc index a7d03a2c3..b6bfba76a 100644 --- a/docs/modules/ROOT/pages/node_pattern.adoc +++ b/docs/modules/ROOT/pages/node_pattern.adoc @@ -486,7 +486,7 @@ current symbol is being called from an initialization method, like: [source,sh] ---- -$ ruby-parse -e 'Comment.new(user: current_user)' +$ ruby-parse --legacy -e 'Comment.new(user: current_user)' (send (const nil :Comment) :new (hash From ab64c602663c93ee2ccaab532cd68772ac48d714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lafortune?= Date: Tue, 23 May 2023 14:24:12 -0400 Subject: [PATCH 6/6] Update docs/modules/ROOT/pages/node_pattern.adoc --- docs/modules/ROOT/pages/node_pattern.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/node_pattern.adoc b/docs/modules/ROOT/pages/node_pattern.adoc index b6bfba76a..0002c6f2c 100644 --- a/docs/modules/ROOT/pages/node_pattern.adoc +++ b/docs/modules/ROOT/pages/node_pattern.adoc @@ -70,7 +70,7 @@ is represented with: Every node is represented with a sequence. The first element is the node type. -Other elements are child nodes. They are optionally present and depend on the node type. +Other elements are the children. They are optionally present and depend on the node type. E.g.: * `nil` is just `(nil)`