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

Improve docs #265

Merged
merged 6 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
----
92 changes: 84 additions & 8 deletions docs/modules/ROOT/pages/node_pattern.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
marcandre marked this conversation as resolved.
Show resolved Hide resolved
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))
----

* `int` will match exactly the node, looking only the node type.
* `(int 1)` will match precisely the node
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))
----

== 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

Expand Down Expand Up @@ -207,6 +271,18 @@ 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 <<Predicate methods>> 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
Expand Down Expand Up @@ -410,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
Expand Down