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) } ---- diff --git a/docs/modules/ROOT/pages/node_pattern.adoc b/docs/modules/ROOT/pages/node_pattern.adoc index 7aa365319..0002c6f2c 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 the children. 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)) ---- -* `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 @@ -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 <> 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 @@ -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