diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5a7c30c9..63d51a3c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,14 @@ on: - pull_request_target jobs: ci: + strategy: + fail-fast: false + matrix: + ruby: + - '2.7' + - '3.0' + - '3.1' + - head name: CI runs-on: ubuntu-latest env: @@ -13,7 +21,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: bundler-cache: true - ruby-version: '3.1' + ruby-version: ${{ matrix.ruby }} - name: Test run: bundle exec rake test automerge: diff --git a/CHANGELOG.md b/CHANGELOG.md index 354776f1..189509c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [2.1.0] - 2022-04-12 + +### Added + +- The `SyntaxTree::Visitor` class now implements the visitor pattern for Ruby nodes. +- The `SyntaxTree::Visitor.visit_method(name)` method. +- Support for Ruby 2.7. +- Support for comments on `rescue` and `else` keywords. +- `SyntaxTree::Location` now additionally has `start_column` and `end_column`. +- The CLI now accepts content over STDIN for the `ast`, `check`, `debug`, `doc`, `format`, and `write` commands. + +### Removed + +- The missing hash value inlay hints have been removed. + ## [2.0.1] - 2022-03-31 ### Changed @@ -128,7 +143,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - 🎉 Initial release! 🎉 -[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.0...HEAD +[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.1.0...HEAD +[2.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.1...v2.1.0 +[2.0.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.2.0...v2.0.0 [1.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.1.1...v1.2.0 [1.1.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.1.0...v1.1.1 diff --git a/Gemfile.lock b/Gemfile.lock index f3ddbb47..989aeb21 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,10 +10,10 @@ GEM benchmark-ips (2.10.0) docile (1.4.0) minitest (5.15.0) - parser (3.1.1.0) + parser (3.1.2.0) ast (~> 2.4.1) rake (13.0.6) - ruby_parser (3.19.0) + ruby_parser (3.19.1) sexp_processor (~> 4.16) sexp_processor (4.16.0) simplecov (0.21.2) @@ -25,6 +25,8 @@ GEM stackprof (0.2.19) PLATFORMS + arm64-darwin-21 + ruby x86_64-darwin-19 x86_64-darwin-21 x86_64-linux diff --git a/README.md b/README.md index 470826d2..32c13b77 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,50 @@ [![Build Status](https://github.com/ruby-syntax-tree/syntax_tree/actions/workflows/main.yml/badge.svg)](https://github.com/ruby-syntax-tree/syntax_tree/actions/workflows/main.yml) [![Gem Version](https://img.shields.io/gem/v/syntax_tree.svg)](https://rubygems.org/gems/syntax_tree) -A fast Ruby parser and formatter with only standard library dependencies. +Syntax Tree is a suite of tools built on top of the internal CRuby parser. It provides the ability to generate a syntax tree from source, as well as the tools necessary to inspect and manipulate that syntax tree. It can be used to build formatters, linters, language servers, and more. + +It is built with only standard library dependencies. It additionally ships with a plugin system so that you can build your own syntax trees from other languages and incorporate these tools. + +- [Installation](#installation) +- [CLI](#cli) + - [ast](#ast) + - [check](#check) + - [format](#format) + - [write](#write) +- [Library](#library) + - [SyntaxTree.read(filepath)](#syntaxtreereadfilepath) + - [SyntaxTree.parse(source)](#syntaxtreeparsesource) + - [SyntaxTree.format(source)](#syntaxtreeformatsource) +- [Nodes](#nodes) + - [child_nodes](#child_nodes) + - [Pattern matching](#pattern-matching) + - [pretty_print(q)](#pretty_printq) + - [to_json(*opts)](#to_jsonopts) + - [format(q)](#formatq) +- [Visitor](#visitor) + - [visit_method](#visit_method) +- [Language server](#language-server) + - [textDocument/formatting](#textdocumentformatting) + - [textDocument/inlayHints](#textdocumentinlayhints) + - [syntaxTree/visualizing](#syntaxtreevisualizing) +- [Contributing](#contributing) +- [License](#license) ## Installation -Add this line to your application's Gemfile: +Syntax Tree is both a command-line interface and a library. If you're only looking to use the command-line interface, then we recommend installing the gem globally, as in: + +```sh +gem install syntax_tree +``` + +To run the CLI with the gem installed globally, you would run: + +```sh +stree version +``` + +If you're planning on using Syntax Tree as a library within your own project, we recommend installing it as part of your gem bundle. First, add this line to your application's Gemfile: ```ruby gem "syntax_tree" @@ -19,52 +58,254 @@ gem "syntax_tree" And then execute: - $ bundle install +```sh +bundle install +``` -Or install it yourself as: +To run the CLI with the gem installed in your gem bundle, you would run: - $ gem install syntax_tree +```sh +bundle exec stree version +``` -## Usage +## CLI -From code: +Syntax Tree ships with the `stree` CLI, which can be used to inspect and manipulate Ruby code. Below are listed all of the commands built into the CLI that you can use. Note that for all commands that operate on files, you can also pass in content through STDIN. -```ruby -require "syntax_tree" +### ast + +This command will print out a textual representation of the syntax tree associated with each of the files it finds. To execute, run: + +```sh +stree ast path/to/file.rb +``` + +For a file that contains `1 + 1`, you will receive: + +``` +(program (statements (binary (int "1") + (int "1")))) +``` + +### check + +This command is meant to be used in the context of a continuous integration or git hook. It checks each file given to make sure that it matches the expected format. It can be used to ensure unformatted content never makes it into a codebase. + +```sh +stree check path/to/file.rb +``` + +For a file that matches the expected format, you will receive: -pp SyntaxTree.parse(source) # print out the AST -puts SyntaxTree.format(source) # format the AST ``` +All files matched expected format. +``` + +If there are files with unformatted code, you will receive: + +``` +[warn] path/to/file.rb +The listed files did not match the expected format. +``` + +### format -From the CLI: +This command will output the formatted version of each of the listed files. Importantly, it will not write that content back to the source files. It is meant to display the formatted version only. ```sh -$ stree ast program.rb -(program - (statements - ... +stree format path/to/file.rb ``` -or +For a file that contains `1 + 1`, you will receive: + +```ruby +1 + 1 +``` + +### write + +This command will format the listed files and write that formatted version back to the source files. Note that this overwrites the original content, to be sure to be using a version control system. ```sh -$ stree format program.rb -class MyClass - ... +stree write path/to/file.rb +``` + +This will list every file that is being formatted. It will output light gray if the file already matches the expected format. It will output in regular color if it does not. + +``` +path/to/file.rb 0ms +``` + +## Library + +Syntax Tree can be used as a library to access the syntax tree underlying Ruby source code. + +### SyntaxTree.read(filepath) + +This function takes a filepath and returns a string associated with the content of that file. It is similar in functionality to `File.read`, except htat it takes into account Ruby-level file encoding (through magic comments at the top of the file). + +### SyntaxTree.parse(source) + +This function takes an input string containing Ruby code and returns the syntax tree associated with it. The top-level node is always a `SyntaxTree::Program`, which contains a list of top-level expression nodes. + +### SyntaxTree.format(source) + +This function takes an input string containing Ruby code, parses it into its underlying syntax tree, and formats it back out to a string. + +## Nodes + +There are many different node types in the syntax tree. They are meant to be treated as immutable structs containing links to child nodes with minimal logic contained within their implementation. However, for the most part they all respond to a certain set of APIs, listed below. + +### child_nodes + +One of the easiest ways to descend the tree is to use the `child_nodes` function. It is implemented on every node type (leaf nodes return an empty array). If the goal is to simply walk through the tree, this is the easiest way to go. + +```ruby +program = SyntaxTree.parse("1 + 1") +program.child_nodes.first.child_nodes.first +# => (binary (int "1") :+ (int "1")) +``` + +### Pattern matching + +Pattern matching is another way to descend the tree which is more specific than using `child_nodes`. Using Ruby's built-in pattern matching, you can extract the same information but be as specific about your constraints as you like. For example, with minimal constraints: + +```ruby +program = SyntaxTree.parse("1 + 1") +program => { statements: { body: [binary] } } +binary +# => (binary (int "1") :+ (int "1")) +``` + +Or, with more constraints on the types to ensure we're getting exactly what we expect: + +```ruby +program = SyntaxTree.parse("1 + 1") +program => SyntaxTree::Program[statements: SyntaxTree::Statements[body: [SyntaxTree::Binary => binary]]] +binary +# => (binary (int "1") :+ (int "1")) +``` + +### pretty_print(q) + +Every node responds to the `pretty_print` Ruby interface, which makes it usable by the `pp` library. You _can_ use this API manually, but it's mostly there for compatibility and not meant to be directly invoked. For example: + +```ruby +pp SyntaxTree.parse("1 + 1") +# (program (statements (binary (int "1") + (int "1")))) +``` + +### to_json(*opts) + +Every node responds to the `to_json` Ruby interface, which makes it usable by the `json` library. Much like `pretty_print`, you could use this API manually, but it's mostly used by `JSON` to dump the nodes to a serialized format. For example: + +```ruby +program = SyntaxTree.parse("1 + 1") +program => { statements: { body: [{ left: }] } } +puts JSON.dump(left) +# {"type":"int","value":"1","loc":[1,0,1,1],"cmts":[]} +``` + +### format(q) + +Every node responds to `format`, which formats the content nicely. The API mirrors that used by the `pretty_print` gem in that it accepts a formatter object and calls methods on it to generate its own internal representation of the text that will be outputted. Because of this, it's easier to not use this API directly and instead to call `SyntaxTree.format`. You _can_ however use this directly if you create the formatter yourself, as in: + +```ruby +source = "1+1" +program = SyntaxTree.parse(source) +program => { statements: { body: [binary] } } + +formatter = SyntaxTree::Formatter.new(source, []) +binary.format(formatter) + +formatter.flush +formatter.output.join +# => "1 + 1" +``` + +## Visitor + +If you want to operate over a set of nodes in the tree but don't want to walk the tree manually, the `Visitor` class makes it easy. `SyntaxTree::Visitor` is an implementation of the double dispatch visitor pattern. It works by the user defining visit methods that process nodes in the tree, which then call back to other visit methods to continue the descent. This is easier shown in code. + +Let's say, for instance, that you wanted to find every place in source where you have an arithmetic problem between two integers (this is pretty contrived, but it's just for illustration). You could define a visitor that only explicitly visits the `SyntaxTree::Binary` node, as in: + +```ruby +class ArithmeticVisitor < SyntaxTree::Visitor + def visit_binary(node) + if node in { left: SyntaxTree::Int, operator: :+ | :- | :* | :/, right: SyntaxTree::Int } + puts "The result is: #{node.left.value.to_i.public_send(node.operator, node.right.value.to_i)}" + end + end +end + +visitor = ArithmeticVisitor.new +visitor.visit(SyntaxTree.parse("1 + 1")) +# The result is: 2 ``` -or +With visitors, you only define handlers for the nodes that you need. You can find the names of the methods that you will need to define within the base visitor, as they're all aliased to the default behavior (visiting the child nodes). Note that when you define a handler for a node, you have to tell Syntax Tree how to walk further. In the example above, we don't need to go any further because we already know the child nodes are `SyntaxTree::Int`, so they can't possibly contain more `SyntaxTree::Binary` nodes. In other circumstances you may not know though, so you can either: + +* call `super` (which will do the default and visit all child nodes) +* call `visit_child_nodes` manually +* call `visit(child)` with each child that you want to visit +* call nothing if you're sure you don't want to descend further + +There are a couple of visitors that ship with Syntax Tree that can be used as examples. They live in the [lib/syntax_tree/visitor](lib/syntax_tree/visitor) directory. + +### visit_method + +When you're creating a visitor, it's very easy to accidentally mistype a visit method. Unfortunately, there's no way to tell Ruby to explicitly override a parent method, so it would then be easy to define a method that never gets called. To mitigate this risk, there's `Visitor.visit_method(name)`. This method accepts a symbol that is checked against the list of known visit methods. If it's not in the list, then an error will be raised. It's meant to be used like: + +```ruby +class ArithmeticVisitor < SyntaxTree::Visitor + visit_method def visit_binary(node) + # ... + end +end +``` + +This will only be checked once when the file is first required. If there is a typo in your method name (or the method no longer exists for whatever reason), you will receive an error like so: + +``` +~/syntax_tree/lib/syntax_tree/visitor.rb:46:in `visit_method': Invalid visit method: visit_binar (SyntaxTree::Visitor::VisitMethodError) +Did you mean? visit_binary + visit_in + visit_ivar + from (irb):2:in `' + from (irb):1:in `
' + from bin/console:8:in `
' +``` + +## Language server + +Syntax Tree additionally ships with a language server conforming to the [language server protocol](https://microsoft.github.io/language-server-protocol/). It can be invoked through the CLI by running: ```sh -$ stree write program.rb -program.rb 1ms +stree lsp +``` + +By default, the language server is relatively minimal, mostly meant to provide a registered formatter for the Ruby language. However there are a couple of additional niceties baked in. There are related projects that configure and use this language server within IDEs. For example, to use this code with VSCode, see [ruby-syntax-tree/vscode-syntax-tree](https://github.com/ruby-syntax-tree/vscode-syntax-tree). + +### textDocument/formatting + +As mentioned above, the language server responds to formatting requests with the formatted document. It typically responds on the order of tens of milliseconds, so it should be fast enough for any IDE. + +### textDocument/inlayHints + +The language server also responds to the relatively new inlay hints request. This request allows the language server to define additional information that should exist in the source code as helpful hints to the developer. In our case we use it to display things like implicit parentheses. For example, if you had the following code: + +```ruby +1 + 2 * 3 ``` -## Development +Implicity, the `2 * 3` is going to be executed first because the `*` operator has higher precedence than the `+` operator. However, to ease mental overhead, our language server includes small parentheses to make this explicit, as in: + +```ruby +1 + ₍2 * 3₎ +``` -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +### syntaxTree/visualizing -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). +The language server additionally includes this custom request to return a textual representation of the syntax tree underlying the source code of a file. Language server clients can use this to (for example) open an additional tab with this information displayed. ## Contributing diff --git a/bin/setup b/bin/setup deleted file mode 100755 index cf4ad25e..00000000 --- a/bin/setup +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' -set -vx - -bundle install diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 1dcfc348..f1df71c5 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "json" require "pp" require "prettyprint" require "ripper" @@ -9,6 +10,9 @@ require_relative "syntax_tree/node" require_relative "syntax_tree/parser" require_relative "syntax_tree/version" +require_relative "syntax_tree/visitor" +require_relative "syntax_tree/visitor/json_visitor" +require_relative "syntax_tree/visitor/pretty_print_visitor" # If PrettyPrint::Align isn't defined, then we haven't gotten the updated # version of prettyprint. In that case we'll define our own. This is going to diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index d6259f3d..1bf09cf7 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -124,7 +124,7 @@ def run(handler, filepath, source) start = Time.now formatted = handler.format(source) - File.write(filepath, formatted) + File.write(filepath, formatted) if filepath != :stdin color = source == formatted ? Color.gray(filepath) : filepath delta = ((Time.now - start) * 1000).round @@ -191,11 +191,6 @@ def run(argv) return 0 end - if arguments.empty? - warn(HELP) - return 1 - end - action = case name when "a", "ast" @@ -215,6 +210,13 @@ def run(argv) return 1 end + # If we're not reading from stdin and the user didn't supply and + # filepaths to be read, then we exit with the usage message. + if STDIN.tty? && arguments.empty? + warn(HELP) + return 1 + end + # If there are any plugins specified on the command line, then load them # by requiring them here. We do this by transforming something like # @@ -224,40 +226,34 @@ def run(argv) # # require "syntax_tree/haml" # - if arguments.first.start_with?("--plugins=") + if arguments.first&.start_with?("--plugins=") plugins = arguments.shift[/^--plugins=(.*)$/, 1] plugins.split(",").each { |plugin| require "syntax_tree/#{plugin}" } end + # Track whether or not there are any errors from any of the files that + # we take action on so that we can properly clean up and exit. errored = false - arguments.each do |pattern| - Dir.glob(pattern).each do |filepath| - next unless File.file?(filepath) - - handler = HANDLERS[File.extname(filepath)] - source = handler.read(filepath) - - begin - action.run(handler, filepath, source) - rescue Parser::ParseError => error - warn("Error: #{error.message}") - - if error.lineno - highlight_error(error, source) - else - warn(error.message) - warn(error.backtrace) - end - - errored = true - rescue Check::UnformattedError, Debug::NonIdempotentFormatError - errored = true - rescue => error - warn(error.message) - warn(error.backtrace) - errored = true - end + + each_file(arguments) do |handler, filepath, source| + action.run(handler, filepath, source) + rescue Parser::ParseError => error + warn("Error: #{error.message}") + + if error.lineno + highlight_error(error, source) + else + warn(error.message) + warn(error.backtrace) end + + errored = true + rescue Check::UnformattedError, Debug::NonIdempotentFormatError + errored = true + rescue => error + warn(error.message) + warn(error.backtrace) + errored = true end if errored @@ -271,6 +267,22 @@ def run(argv) private + def each_file(arguments) + if STDIN.tty? + arguments.each do |pattern| + Dir.glob(pattern).each do |filepath| + next unless File.file?(filepath) + + handler = HANDLERS[File.extname(filepath)] + source = handler.read(filepath) + yield handler, filepath, source + end + end + else + yield HANDLERS[".rb"], :stdin, STDIN.read + end + end + # Highlights a snippet from a source and parse error. def highlight_error(error, source) lines = source.lines diff --git a/lib/syntax_tree/language_server/inlay_hints.rb b/lib/syntax_tree/language_server/inlay_hints.rb index 5e43439c..0bed2a80 100644 --- a/lib/syntax_tree/language_server/inlay_hints.rb +++ b/lib/syntax_tree/language_server/inlay_hints.rb @@ -27,20 +27,6 @@ def bare_rescue(location) after[location.start_char + "rescue".length] << " StandardError" end - # Adds the implicitly referenced value (local variable or method call) - # that is added into a hash when the value of a key-value pair is omitted. - # For example, - # - # { value: } - # - # becomes - # - # { value: value } - # - def missing_hash_value(key, location) - after[location.end_char] << " #{key}" - end - # Adds implicit parentheses around certain expressions to make it clear # which subexpression will be evaluated first. For example, # @@ -69,8 +55,6 @@ def self.find(program) case [parent_node, child_node] in _, Rescue[exception: nil, location:] inlay_hints.bare_rescue(location) - in _, Assoc[key: Label[value: key], value: nil, location:] - inlay_hints.missing_hash_value(key[0...-1], location) in Assign | Binary | IfOp | OpAssign, IfOp[location:] inlay_hints.precedence_parentheses(location) in Assign | OpAssign, Binary[location:] diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 937e1981..fa4e0829 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -3,13 +3,15 @@ module SyntaxTree # Represents the location of a node in the tree from the source code. class Location - attr_reader :start_line, :start_char, :end_line, :end_char + attr_reader :start_line, :start_char, :start_column, :end_line, :end_char, :end_column - def initialize(start_line:, start_char:, end_line:, end_char:) + def initialize(start_line:, start_char:, start_column:, end_line:, end_char:, end_column:) @start_line = start_line @start_char = start_char + @start_column = start_column @end_line = end_line @end_char = end_char + @end_column = end_column end def lines @@ -26,26 +28,26 @@ def to(other) Location.new( start_line: start_line, start_char: start_char, + start_column: start_column, end_line: [end_line, other.end_line].max, - end_char: other.end_char + end_char: other.end_char, + end_column: other.end_column ) end - def to_json(*opts) - [start_line, start_char, end_line, end_char].to_json(*opts) - end - - def self.token(line:, char:, size:) + def self.token(line:, char:, column:, size:) new( start_line: line, start_char: char, + start_column: column, end_line: line, - end_char: char + size + end_char: char + size, + end_column: column + size ) end - def self.fixed(line:, char:) - new(start_line: line, start_char: char, end_line: line, end_char: char) + def self.fixed(line:, char:, column:) + new(start_line: line, start_char: char, start_column: column, end_line: line, end_char: char, end_column: column) end end @@ -56,6 +58,10 @@ class Node # [Location] the location of this node attr_reader :location + def accept(visitor) + raise NotImplementedError + end + def child_nodes raise NotImplementedError end @@ -73,11 +79,13 @@ def format(q) end def pretty_print(q) - raise NotImplementedError + visitor = Visitor::PrettyPrintVisitor.new(q) + visitor.visit(self) end def to_json(*opts) - raise NotImplementedError + visitor = Visitor::JSONVisitor.new + visitor.visit(self).to_json(*opts) end end @@ -107,6 +115,10 @@ def initialize(lbrace:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_BEGIN(self) + end + def child_nodes [lbrace, statements] end @@ -134,27 +146,6 @@ def format(q) q.text("}") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("BEGIN") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :BEGIN, - lbrace: lbrace, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # CHAR irepresents a single codepoint in the script encoding. @@ -176,6 +167,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_CHAR(self) + end + def child_nodes [] end @@ -195,23 +190,6 @@ def format(q) q.text(q.quote) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("CHAR") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :CHAR, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ENDBlock represents the use of the +END+ keyword, which hooks into the @@ -240,6 +218,10 @@ def initialize(lbrace:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_END(self) + end + def child_nodes [lbrace, statements] end @@ -267,27 +249,6 @@ def format(q) q.text("}") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("END") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :END, - lbrace: lbrace, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # EndContent represents the use of __END__ syntax, which allows individual @@ -312,6 +273,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit___end__(self) + end + def child_nodes [] end @@ -329,23 +294,6 @@ def format(q) separator = -> { q.breakable(indent: false, force: true) } q.seplist(value.split(/\r?\n/, -1), separator) { |line| q.text(line) } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("__end__") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :__end__, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Alias represents the use of the +alias+ keyword with regular arguments (not @@ -401,6 +349,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_alias(self) + end + def child_nodes [left, right] end @@ -426,30 +378,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("alias") - - q.breakable - q.pp(left) - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :alias, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ARef represents when you're pulling a value out of a collection at a @@ -482,6 +410,10 @@ def initialize(collection:, index:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_aref(self) + end + def child_nodes [collection, index] end @@ -513,30 +445,6 @@ def format(q) q.text("]") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("aref") - - q.breakable - q.pp(collection) - - q.breakable - q.pp(index) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :aref, - collection: collection, - index: index, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ARefField represents assigning values into collections at specific indices. @@ -563,6 +471,10 @@ def initialize(collection:, index:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_aref_field(self) + end + def child_nodes [collection, index] end @@ -594,30 +506,6 @@ def format(q) q.text("]") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("aref_field") - - q.breakable - q.pp(collection) - - q.breakable - q.pp(index) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :aref_field, - collection: collection, - index: index, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ArgParen represents wrapping arguments to a method inside a set of @@ -645,6 +533,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_arg_paren(self) + end + def child_nodes [arguments] end @@ -669,26 +561,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("arg_paren") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :arg_paren, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Args represents a list of arguments being passed to a method call or array @@ -709,6 +581,10 @@ def initialize(parts:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_args(self) + end + def child_nodes parts end @@ -722,23 +598,6 @@ def deconstruct_keys(keys) def format(q) q.seplist(parts) { |part| q.format(part) } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("args") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :args, parts: parts, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ArgBlock represents using a block operator on an expression. @@ -758,6 +617,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_arg_block(self) + end + def child_nodes [value] end @@ -772,25 +635,6 @@ def format(q) q.text("&") q.format(value) if value end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("arg_block") - - if value - q.breakable - q.pp(value) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :arg_block, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Star represents using a splat operator on an expression. @@ -810,6 +654,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_arg_star(self) + end + def child_nodes [value] end @@ -824,23 +672,6 @@ def format(q) q.text("*") q.format(value) if value end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("arg_star") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :arg_star, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ArgsForward represents forwarding all kinds of arguments onto another method @@ -873,6 +704,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_args_forward(self) + end + def child_nodes [] end @@ -886,26 +721,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("args_forward") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :args_forward, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ArrayLiteral represents an array literal, which can optionally contain @@ -1002,6 +817,10 @@ def initialize(lbracket:, contents:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_array(self) + end + def child_nodes [lbracket, contents] end @@ -1048,23 +867,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("array") - - q.breakable - q.pp(contents) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :array, cnts: contents, loc: location, cmts: comments }.to_json( - *opts - ) - end - private def qwords? @@ -1174,6 +976,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_aryptn(self) + end + def child_nodes [constant, *requireds, rest, *posts] end @@ -1216,48 +1022,6 @@ def format(q) q.group { q.seplist(parts) { |part| q.format(part) } } end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("aryptn") - - if constant - q.breakable - q.pp(constant) - end - - if requireds.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(requireds) { |required| q.pp(required) } - end - end - - if rest - q.breakable - q.pp(rest) - end - - if posts.any? - q.breakable - q.group(2, "(", ")") { q.seplist(posts) { |post| q.pp(post) } } - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :aryptn, - constant: constant, - reqs: requireds, - rest: rest, - posts: posts, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Determins if the following value should be indented or not. @@ -1301,6 +1065,10 @@ def initialize(target:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_assign(self) + end + def child_nodes [target, value] end @@ -1328,37 +1096,13 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("assign") - - q.breakable - q.pp(target) - - q.breakable - q.pp(value) + private - q.pp(Comment::List.new(comments)) - end + def skip_indent? + target.comments.empty? && + (target.is_a?(ARefField) || AssignFormatting.skip_indent?(value)) end - - def to_json(*opts) - { - type: :assign, - target: target, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end - - private - - def skip_indent? - target.comments.empty? && - (target.is_a?(ARefField) || AssignFormatting.skip_indent?(value)) - end - end + end # Assoc represents a key-value pair within a hash. It is a child node of # either an AssocListFromArgs or a BareAssocHash. @@ -1383,6 +1127,10 @@ def initialize(key:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_assoc(self) + end + def child_nodes [key, value] end @@ -1401,32 +1149,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("assoc") - - q.breakable - q.pp(key) - - if value - q.breakable - q.pp(value) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :assoc, - key: key, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end - private def format_contents(q) @@ -1463,6 +1185,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_assoc_splat(self) + end + def child_nodes [value] end @@ -1477,26 +1203,6 @@ def format(q) q.text("**") q.format(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("assoc_splat") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :assoc_splat, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Backref represents a global variable referencing a matched value. It comes @@ -1517,6 +1223,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_backref(self) + end + def child_nodes [] end @@ -1530,23 +1240,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("backref") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :backref, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Backtick represents the use of the ` operator. It's usually found being used @@ -1565,6 +1258,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_backtick(self) + end + def child_nodes [] end @@ -1578,23 +1275,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("backtick") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :backtick, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # This module is responsible for formatting the assocs contained within a @@ -1667,7 +1347,7 @@ def self.for(container) # method(key1: value1, key2: value2) # class BareAssocHash < Node - # [Array[ AssocNew | AssocSplat ]] + # [Array[ Assoc | AssocSplat ]] attr_reader :assocs # [Array[ Comment | EmbDoc ]] the comments attached to this node @@ -1679,6 +1359,10 @@ def initialize(assocs:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_bare_assoc_hash(self) + end + def child_nodes assocs end @@ -1696,26 +1380,6 @@ def format(q) def format_key(q, key) (@key_formatter ||= HashKeyFormatter.for(self)).format_key(q, key) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("bare_assoc_hash") - - q.breakable - q.group(2, "(", ")") { q.seplist(assocs) { |assoc| q.pp(assoc) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :bare_assoc_hash, - assocs: assocs, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Begin represents a begin..end chain. @@ -1737,6 +1401,10 @@ def initialize(bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_begin(self) + end + def child_nodes [bodystmt] end @@ -1760,26 +1428,6 @@ def format(q) q.breakable(force: true) q.text("end") end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("begin") - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :begin, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # PinnedBegin represents a pinning a nested statement within pattern matching. @@ -1801,6 +1449,10 @@ def initialize(statement:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_pinned_begin(self) + end + def child_nodes [statement] end @@ -1824,29 +1476,8 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("pinned_begin") - - q.breakable - q.pp(statement) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :pinned_begin, - stmt: statement, - loc: location, - cmts: comments - }.to_json(*opts) - end end - # Binary represents any expression that involves two sub-expressions with an # operator in between. This can be something that looks like a mathematical # operation: @@ -1878,6 +1509,10 @@ def initialize(left:, operator:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_binary(self) + end + def child_nodes [left, right] end @@ -1901,44 +1536,22 @@ def format(q) q.group { q.format(left) } q.text(" ") unless power - q.group do + if operator == :<< q.text(operator) + q.text(" ") + q.format(right) + else + q.group do + q.text(operator) - q.indent do - q.breakable(power ? "" : " ") - q.format(right) + q.indent do + q.breakable(power ? "" : " ") + q.format(right) + end end end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("binary") - - q.breakable - q.pp(left) - - q.breakable - q.text(operator) - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :binary, - left: left, - op: operator, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # This module will remove any breakables from the list of contents so that no @@ -2011,6 +1624,10 @@ def initialize(params:, locals:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_block_var(self) + end + def child_nodes [params, *locals] end @@ -2032,32 +1649,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("block_var") - - q.breakable - q.pp(params) - - if locals.any? - q.breakable - q.group(2, "(", ")") { q.seplist(locals) { |local| q.pp(local) } } - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :block_var, - params: params, - locals: locals, - loc: location, - cmts: comments - }.to_json(*opts) - end end # BlockArg represents declaring a block parameter on a method definition. @@ -2077,6 +1668,10 @@ def initialize(name:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_blockarg(self) + end + def child_nodes [name] end @@ -2091,25 +1686,6 @@ def format(q) q.text("&") q.format(name) if name end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("blockarg") - - if name - q.breakable - q.pp(name) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :blockarg, name: name, loc: location, cmts: comments }.to_json( - *opts - ) - end end # bodystmt can't actually determine its bounds appropriately because it @@ -2122,6 +1698,9 @@ class BodyStmt < Node # [nil | Rescue] the optional rescue chain attached to the begin clause attr_reader :rescue_clause + # [nil | Kw] the optional else keyword + attr_reader :else_keyword + # [nil | Statements] the optional set of statements inside the else clause attr_reader :else_clause @@ -2134,6 +1713,7 @@ class BodyStmt < Node def initialize( statements:, rescue_clause:, + else_keyword:, else_clause:, ensure_clause:, location:, @@ -2141,19 +1721,22 @@ def initialize( ) @statements = statements @rescue_clause = rescue_clause + @else_keyword = else_keyword @else_clause = else_clause @ensure_clause = ensure_clause @location = location @comments = comments end - def bind(start_char, end_char) + def bind(start_char, start_column, end_char, end_column) @location = Location.new( start_line: location.start_line, start_char: start_char, + start_column: start_column, end_line: location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) parts = [rescue_clause, else_clause, ensure_clause] @@ -2162,14 +1745,17 @@ def bind(start_char, end_char) consequent = parts.compact.first statements.bind( start_char, - consequent ? consequent.location.start_char : end_char + start_column, + consequent ? consequent.location.start_char : end_char, + consequent ? consequent.location.start_column : end_column ) # Next we're going to determine the rescue clause if there is one if rescue_clause consequent = parts.drop(1).compact.first rescue_clause.bind_end( - consequent ? consequent.location.start_char : end_char + consequent ? consequent.location.start_char : end_char, + consequent ? consequent.location.start_column : end_column ) end end @@ -2178,8 +1764,12 @@ def empty? statements.empty? && !rescue_clause && !else_clause && !ensure_clause end + def accept(visitor) + visitor.visit_bodystmt(self) + end + def child_nodes - [statements, rescue_clause, else_clause, ensure_clause] + [statements, rescue_clause, else_keyword, else_clause, ensure_clause] end alias deconstruct child_nodes @@ -2209,10 +1799,13 @@ def format(q) if else_clause q.nest(-2) do q.breakable(force: true) - q.text("else") + q.format(else_keyword) + end + + unless else_clause.empty? + q.breakable(force: true) + q.format(else_clause) end - q.breakable(force: true) - q.format(else_clause) end if ensure_clause @@ -2223,44 +1816,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("bodystmt") - - q.breakable - q.pp(statements) - - if rescue_clause - q.breakable - q.pp(rescue_clause) - end - - if else_clause - q.breakable - q.pp(else_clause) - end - - if ensure_clause - q.breakable - q.pp(ensure_clause) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :bodystmt, - stmts: statements, - rsc: rescue_clause, - els: else_clause, - ens: ensure_clause, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Responsible for formatting either a BraceBlock or a DoBlock. @@ -2447,6 +2002,10 @@ def initialize(lbrace:, block_var:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_brace_block(self) + end + def child_nodes [lbrace, block_var, statements] end @@ -2466,33 +2025,6 @@ def deconstruct_keys(keys) def format(q) BlockFormatter.new(self, lbrace, "}", statements).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("brace_block") - - if block_var - q.breakable - q.pp(block_var) - end - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :brace_block, - lbrace: lbrace, - block_var: block_var, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Formats either a Break or Next node. @@ -2567,6 +2099,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_break(self) + end + def child_nodes [arguments] end @@ -2580,23 +2116,6 @@ def deconstruct_keys(keys) def format(q) FlowControlFormatter.new("break", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("break") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :break, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Wraps a call operator (which can be a string literal :: or an Op node or a @@ -2658,6 +2177,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_call(self) + end + def child_nodes [ receiver, @@ -2707,41 +2230,7 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("call") - - q.breakable - q.pp(receiver) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(message) - - if arguments - q.breakable - q.pp(arguments) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :call, - receiver: receiver, - op: operator, - message: message, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end - end + end # Case represents the beginning of a case chain. # @@ -2775,6 +2264,10 @@ def initialize(keyword:, value:, consequent:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_case(self) + end + def child_nodes [keyword, value, consequent] end @@ -2807,35 +2300,6 @@ def format(q) q.text("end") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("case") - - q.breakable - q.pp(keyword) - - if value - q.breakable - q.pp(value) - end - - q.breakable - q.pp(consequent) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :case, - value: value, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # RAssign represents a single-line pattern match. @@ -2865,6 +2329,10 @@ def initialize(value:, operator:, pattern:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rassign(self) + end + def child_nodes [value, operator, pattern] end @@ -2894,34 +2362,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rassign") - - q.breakable - q.pp(value) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(pattern) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :rassign, - value: value, - op: operator, - pattern: pattern, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Class represents defining a class using the +class+ keyword. @@ -2978,6 +2418,10 @@ def initialize(constant:, superclass:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_class(self) + end + def child_nodes [constant, superclass, bodystmt] end @@ -3027,36 +2471,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("class") - - q.breakable - q.pp(constant) - - if superclass - q.breakable - q.pp(superclass) - end - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :class, - constant: constant, - superclass: superclass, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Comma represents the use of the , operator. @@ -3068,6 +2482,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_comma(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # Command represents a method call with arguments and no parentheses. Note @@ -3093,6 +2521,10 @@ def initialize(message:, arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_command(self) + end + def child_nodes [message, arguments] end @@ -3112,33 +2544,27 @@ def format(q) q.group do q.format(message) q.text(" ") - q.nest(message.value.length + 1) { q.format(arguments) } + + if align?(self) + q.nest(message.value.length + 1) { q.format(arguments) } + else + q.format(arguments) + end end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("command") - - q.breakable - q.pp(message) - - q.breakable - q.pp(arguments) + private - q.pp(Comment::List.new(comments)) + def align?(node) + case node.arguments + in Args[parts: [Def | Defs | DefEndless]] + false + in Args[parts: [Command => command]] + align?(command) + else + true end end - - def to_json(*opts) - { - type: :command, - message: message, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end end # CommandCall represents a method call on an object with arguments and no @@ -3178,6 +2604,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_command_call(self) + end + def child_nodes [receiver, message, arguments] end @@ -3211,40 +2641,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("command_call") - - q.breakable - q.pp(receiver) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(message) - - if arguments - q.breakable - q.pp(arguments) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :command_call, - receiver: receiver, - op: operator, - message: message, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end - private # This is a somewhat naive method that is attempting to sum up the width of @@ -3300,22 +2696,6 @@ def argument_alignment(q, doc) # # comment # class Comment < Node - class List - # [Array[ Comment ]] the list of comments this list represents - attr_reader :comments - - def initialize(comments) - @comments = comments - end - - def pretty_print(q) - return if comments.empty? - - q.breakable - q.group(2, "(", ")") { q.seplist(comments) { |comment| q.pp(comment) } } - end - end - # [String] the contents of the comment attr_reader :value @@ -3357,6 +2737,10 @@ def comments [] end + def accept(visitor) + visitor.visit_comment(self) + end + def child_nodes [] end @@ -3370,24 +2754,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("comment") - - q.breakable - q.pp(value) - end - end - - def to_json(*opts) - { - type: :comment, - value: value.force_encoding("UTF-8"), - inline: inline, - loc: location - }.to_json(*opts) - end end # Const represents a literal value that _looks_ like a constant. This could @@ -3417,6 +2783,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_const(self) + end + def child_nodes [] end @@ -3430,23 +2800,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("const") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :const, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ConstPathField represents the child node of some kind of assignment. It @@ -3472,6 +2825,10 @@ def initialize(parent:, constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_const_path_field(self) + end + def child_nodes [parent, constant] end @@ -3492,30 +2849,6 @@ def format(q) q.text("::") q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("const_path_field") - - q.breakable - q.pp(parent) - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :const_path_field, - parent: parent, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ConstPathRef represents referencing a constant by a path. @@ -3539,6 +2872,10 @@ def initialize(parent:, constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_const_path_ref(self) + end + def child_nodes [parent, constant] end @@ -3559,30 +2896,6 @@ def format(q) q.text("::") q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("const_path_ref") - - q.breakable - q.pp(parent) - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :const_path_ref, - parent: parent, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ConstRef represents the name of the constant being used in a class or module @@ -3604,6 +2917,10 @@ def initialize(constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_const_ref(self) + end + def child_nodes [constant] end @@ -3617,26 +2934,6 @@ def deconstruct_keys(keys) def format(q) q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("const_ref") - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :const_ref, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # CVar represents the use of a class variable. @@ -3656,6 +2953,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_cvar(self) + end + def child_nodes [] end @@ -3669,23 +2970,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("cvar") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :cvar, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Def represents defining a regular method on the current self object. @@ -3713,6 +2997,10 @@ def initialize(name:, params:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_def(self) + end + def child_nodes [name, params, bodystmt] end @@ -3748,49 +3036,21 @@ def format(q) q.text("end") end end + end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("def") + # DefEndless represents defining a single-line method since Ruby 3.0+. + # + # def method = result + # + class DefEndless < Node + # [untyped] the target where the method is being defined + attr_reader :target - q.breakable - q.pp(name) + # [Op | Period] the operator being used to declare the method + attr_reader :operator - q.breakable - q.pp(params) - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :def, - name: name, - params: params, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end - end - - # DefEndless represents defining a single-line method since Ruby 3.0+. - # - # def method = result - # - class DefEndless < Node - # [untyped] the target where the method is being defined - attr_reader :target - - # [Op | Period] the operator being used to declare the method - attr_reader :operator - - # [Backtick | Const | Ident | Kw | Op] the name of the method - attr_reader :name + # [Backtick | Const | Ident | Kw | Op] the name of the method + attr_reader :name # [nil | Params | Paren] the parameter declaration for the method attr_reader :paren @@ -3819,6 +3079,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_def_endless(self) + end + def child_nodes [target, operator, name, paren, statement] end @@ -3863,44 +3127,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("def_endless") - - if target - q.breakable - q.pp(target) - - q.breakable - q.pp(operator) - end - - q.breakable - q.pp(name) - - if paren - q.breakable - q.pp(paren) - end - - q.breakable - q.pp(statement) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :def_endless, - name: name, - paren: paren, - stmt: statement, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Defined represents the use of the +defined?+ operator. It can be used with @@ -3921,6 +3147,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_defined(self) + end + def child_nodes [value] end @@ -3940,23 +3170,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("defined") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :defined, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Defs represents defining a singleton method on an object. @@ -4000,6 +3213,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_defs(self) + end + def child_nodes [target, operator, name, params, bodystmt] end @@ -4039,42 +3256,6 @@ def format(q) q.text("end") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("defs") - - q.breakable - q.pp(target) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(name) - - q.breakable - q.pp(params) - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :defs, - target: target, - op: operator, - name: name, - params: params, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # DoBlock represents passing a block to a method call using the +do+ and +end+ @@ -4104,6 +3285,10 @@ def initialize(keyword:, block_var:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_do_block(self) + end + def child_nodes [keyword, block_var, bodystmt] end @@ -4123,33 +3308,6 @@ def deconstruct_keys(keys) def format(q) BlockFormatter.new(self, keyword, "end", bodystmt).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("do_block") - - if block_var - q.breakable - q.pp(block_var) - end - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :do_block, - keyword: keyword, - block_var: block_var, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Responsible for formatting Dot2 and Dot3 nodes. @@ -4207,6 +3365,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_dot2(self) + end + def child_nodes [left, right] end @@ -4220,34 +3382,6 @@ def deconstruct_keys(keys) def format(q) DotFormatter.new("..", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("dot2") - - if left - q.breakable - q.pp(left) - end - - if right - q.breakable - q.pp(right) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :dot2, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Dot3 represents using the ... operator between two expressions. Usually this @@ -4279,6 +3413,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_dot3(self) + end + def child_nodes [left, right] end @@ -4292,34 +3430,6 @@ def deconstruct_keys(keys) def format(q) DotFormatter.new("...", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("dot3") - - if left - q.breakable - q.pp(left) - end - - if right - q.breakable - q.pp(right) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :dot3, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Responsible for providing information about quotes to be used for strings @@ -4390,6 +3500,10 @@ def initialize(parts:, quote:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_dyna_symbol(self) + end + def child_nodes parts end @@ -4418,27 +3532,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("dyna_symbol") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :dyna_symbol, - parts: parts, - quote: quote, - loc: location, - cmts: comments - }.to_json(*opts) - end - private # Here we determine the quotes to use for a dynamic symbol. It's bound by a @@ -4486,31 +3579,44 @@ def quotes(q) # end # class Else < Node + # [Kw] the else keyword + attr_reader :keyword + # [Statements] the expressions to be executed attr_reader :statements # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(statements:, location:, comments: []) + def initialize(keyword:, statements:, location:, comments: []) + @keyword = keyword @statements = statements @location = location @comments = comments end + def accept(visitor) + visitor.visit_else(self) + end + def child_nodes - [statements] + [keyword, statements] end alias deconstruct child_nodes def deconstruct_keys(keys) - { statements: statements, location: location, comments: comments } + { + keyword: keyword, + statements: statements, + location: location, + comments: comments + } end def format(q) q.group do - q.text("else") + q.format(keyword) unless statements.empty? q.indent do @@ -4520,23 +3626,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("else") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :else, stmts: statements, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Elsif represents another clause in an +if+ or +unless+ chain. @@ -4572,6 +3661,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_elsif(self) + end + def child_nodes [predicate, statements, consequent] end @@ -4610,36 +3703,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("elsif") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :elsif, - pred: predicate, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # EmbDoc represents a multi-line comment. @@ -4670,6 +3733,10 @@ def comments [] end + def accept(visitor) + visitor.visit_embdoc(self) + end + def child_nodes [] end @@ -4684,19 +3751,6 @@ def format(q) q.trim q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("embdoc") - - q.breakable - q.pp(value) - end - end - - def to_json(*opts) - { type: :embdoc, value: value, loc: location }.to_json(*opts) - end end # EmbExprBeg represents the beginning token for using interpolation inside of @@ -4713,6 +3767,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_embexpr_beg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # EmbExprEnd represents the ending token for using interpolation inside of a @@ -4729,6 +3797,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_embexpr_end(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # EmbVar represents the use of shorthand interpolation for an instance, class, @@ -4747,6 +3829,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_embvar(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # Ensure represents the use of the +ensure+ keyword and its subsequent @@ -4773,6 +3869,10 @@ def initialize(keyword:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_ensure(self) + end + def child_nodes [keyword, statements] end @@ -4798,27 +3898,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("ensure") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :ensure, - keyword: keyword, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ExcessedComma represents a trailing comma in a list of block parameters. It @@ -4844,6 +3923,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_excessed_comma(self) + end + def child_nodes [] end @@ -4857,26 +3940,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("excessed_comma") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :excessed_comma, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # FCall represents the piece of a method call that comes before any arguments @@ -4903,6 +3966,10 @@ def initialize(value:, arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_fcall(self) + end + def child_nodes [value, arguments] end @@ -4922,32 +3989,6 @@ def format(q) q.format(value) q.format(arguments) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("fcall") - - q.breakable - q.pp(value) - - if arguments - q.breakable - q.pp(arguments) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :fcall, - value: value, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Field is always the child of an assignment. It represents assigning to a @@ -4976,6 +4017,10 @@ def initialize(parent:, operator:, name:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_field(self) + end + def child_nodes [parent, (operator if operator != :"::"), name] end @@ -4999,34 +4044,6 @@ def format(q) q.format(name) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("field") - - q.breakable - q.pp(parent) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(name) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :field, - parent: parent, - op: operator, - name: name, - loc: location, - cmts: comments - }.to_json(*opts) - end end # FloatLiteral represents a floating point number literal. @@ -5046,6 +4063,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_float(self) + end + def child_nodes [] end @@ -5059,23 +4080,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("float") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :float, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # FndPtn represents matching against a pattern where you find a pattern in an @@ -5111,6 +4115,10 @@ def initialize(constant:, left:, values:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_fndptn(self) + end + def child_nodes [constant, left, *values, right] end @@ -5142,40 +4150,6 @@ def format(q) q.format(right) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("fndptn") - - if constant - q.breakable - q.pp(constant) - end - - q.breakable - q.pp(left) - - q.breakable - q.group(2, "(", ")") { q.seplist(values) { |value| q.pp(value) } } - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :fndptn, - constant: constant, - left: left, - values: values, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # For represents using a +for+ loop. @@ -5205,6 +4179,10 @@ def initialize(index:, collection:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_for(self) + end + def child_nodes [index, collection, statements] end @@ -5239,34 +4217,6 @@ def format(q) q.text("end") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("for") - - q.breakable - q.pp(index) - - q.breakable - q.pp(collection) - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :for, - index: index, - collection: collection, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # GVar represents a global variable literal. @@ -5286,6 +4236,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_gvar(self) + end + def child_nodes [] end @@ -5299,23 +4253,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("gvar") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :gvar, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # HashLiteral represents a hash literal. @@ -5339,6 +4276,10 @@ def initialize(lbrace:, assocs:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_hash(self) + end + def child_nodes [lbrace] + assocs end @@ -5361,25 +4302,6 @@ def format_key(q, key) (@key_formatter ||= HashKeyFormatter.for(self)).format_key(q, key) end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("hash") - - if assocs.any? - q.breakable - q.group(2, "(", ")") { q.seplist(assocs) { |assoc| q.pp(assoc) } } - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :hash, assocs: assocs, loc: location, cmts: comments }.to_json( - *opts - ) - end - private def format_contents(q) @@ -5427,6 +4349,10 @@ def initialize(beginning:, ending: nil, parts: [], location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_heredoc(self) + end + def child_nodes [beginning, *parts] end @@ -5473,28 +4399,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("heredoc") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :heredoc, - beging: beginning, - ending: ending, - parts: parts, - loc: location, - cmts: comments - }.to_json(*opts) - end end # HeredocBeg represents the beginning declaration of a heredoc. @@ -5517,6 +4421,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_heredoc_beg(self) + end + def child_nodes [] end @@ -5530,26 +4438,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("heredoc_beg") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :heredoc_beg, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # HshPtn represents matching against a hash pattern using the Ruby 2.7+ @@ -5625,6 +4513,10 @@ def initialize(constant:, keywords:, keyword_rest:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_hshptn(self) + end + def child_nodes [constant, *keywords.flatten(1), keyword_rest] end @@ -5663,51 +4555,6 @@ def format(q) contents.call end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("hshptn") - - if constant - q.breakable - q.pp(constant) - end - - if keywords.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(keywords) do |(key, value)| - q.group(2, "(", ")") do - q.pp(key) - - if value - q.breakable - q.pp(value) - end - end - end - end - end - - if keyword_rest - q.breakable - q.pp(keyword_rest) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :hshptn, - constant: constant, - keywords: keywords, - kwrest: keyword_rest, - loc: location, - cmts: comments - }.to_json(*opts) - end end # The list of nodes that represent patterns inside of pattern matching so that @@ -5732,6 +4579,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_ident(self) + end + def child_nodes [] end @@ -5745,26 +4596,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("ident") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :ident, - value: value.force_encoding("UTF-8"), - loc: location, - cmts: comments - }.to_json(*opts) - end end # If the predicate of a conditional or loop contains an assignment (in which @@ -5877,6 +4708,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_if(self) + end + def child_nodes [predicate, statements, consequent] end @@ -5896,37 +4731,7 @@ def deconstruct_keys(keys) def format(q) ConditionalFormatter.new("if", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("if") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :if, - pred: predicate, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end - end + end # IfOp represents a ternary clause. # @@ -5953,6 +4758,10 @@ def initialize(predicate:, truthy:, falsy:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_if_op(self) + end + def child_nodes [predicate, truthy, falsy] end @@ -5985,34 +4794,6 @@ def format(q) q.group { q.if_break { format_break(q) }.if_flat { format_flat(q) } } end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("ifop") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(truthy) - - q.breakable - q.pp(falsy) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :ifop, - pred: predicate, - tthy: truthy, - flsy: falsy, - loc: location, - cmts: comments - }.to_json(*opts) - end - private def format_break(q) @@ -6115,6 +4896,10 @@ def initialize(statement:, predicate:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_if_mod(self) + end + def child_nodes [statement, predicate] end @@ -6133,30 +4918,6 @@ def deconstruct_keys(keys) def format(q) ConditionalModFormatter.new("if", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("if_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(predicate) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :if_mod, - stmt: statement, - pred: predicate, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Imaginary represents an imaginary number literal. @@ -6176,6 +4937,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_imaginary(self) + end + def child_nodes [] end @@ -6189,23 +4954,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("imaginary") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :imaginary, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # In represents using the +in+ keyword within the Ruby 2.7+ pattern matching @@ -6236,6 +4984,10 @@ def initialize(pattern:, statements:, consequent:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_in(self) + end + def child_nodes [pattern, statements, consequent] end @@ -6272,36 +5024,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("in") - - q.breakable - q.pp(pattern) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :in, - pattern: pattern, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Int represents an integer number literal. @@ -6321,6 +5043,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_int(self) + end + def child_nodes [] end @@ -6342,21 +5068,6 @@ def format(q) q.text(value) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("int") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :int, value: value, loc: location, cmts: comments }.to_json(*opts) - end end # IVar represents an instance variable literal. @@ -6376,6 +5087,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_ivar(self) + end + def child_nodes [] end @@ -6389,23 +5104,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("ivar") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :ivar, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Kw represents the use of a keyword. It can be almost anywhere in the syntax @@ -6434,6 +5132,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_kw(self) + end + def child_nodes [] end @@ -6447,21 +5149,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("kw") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :kw, value: value, loc: location, cmts: comments }.to_json(*opts) - end end # KwRestParam represents defining a parameter in a method definition that @@ -6482,6 +5169,10 @@ def initialize(name:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_kwrest_param(self) + end + def child_nodes [name] end @@ -6496,26 +5187,6 @@ def format(q) q.text("**") q.format(name) if name end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("kwrest_param") - - q.breakable - q.pp(name) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :kwrest_param, - name: name, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Label represents the use of an identifier to associate with an object. You @@ -6544,6 +5215,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_label(self) + end + def child_nodes [] end @@ -6557,24 +5232,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("label") - - q.breakable - q.text(":") - q.text(value[0...-1]) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :label, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # LabelEnd represents the end of a dynamic symbol. @@ -6592,6 +5249,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_label_end(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # Lambda represents using a lambda literal (not the lambda method call). @@ -6615,6 +5286,10 @@ def initialize(params:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_lambda(self) + end + def child_nodes [params, statements] end @@ -6664,30 +5339,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("lambda") - - q.breakable - q.pp(params) - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :lambda, - params: params, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # LBrace represents the use of a left brace, i.e., {. @@ -6704,6 +5355,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_lbrace(self) + end + def child_nodes [] end @@ -6717,23 +5372,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("lbrace") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :lbrace, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # LBracket represents the use of a left bracket, i.e., [. @@ -6750,6 +5388,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_lbracket(self) + end + def child_nodes [] end @@ -6763,23 +5405,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("lbracket") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :lbracket, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # LParen represents the use of a left parenthesis, i.e., (. @@ -6796,6 +5421,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_lparen(self) + end + def child_nodes [] end @@ -6809,23 +5438,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("lparen") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :lparen, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # MAssign is a parent node of any kind of multiple assignment. This includes @@ -6859,6 +5471,10 @@ def initialize(target:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_massign(self) + end + def child_nodes [target, value] end @@ -6879,30 +5495,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("massign") - - q.breakable - q.pp(target) - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :massign, - target: target, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # MethodAddBlock represents a method call with a block argument. @@ -6926,6 +5518,10 @@ def initialize(call:, block:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_method_add_block(self) + end + def child_nodes [call, block] end @@ -6940,31 +5536,7 @@ def format(q) q.format(call) q.format(block) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("method_add_block") - - q.breakable - q.pp(call) - - q.breakable - q.pp(block) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :method_add_block, - call: call, - block: block, - loc: location, - cmts: comments - }.to_json(*opts) - end - end + end # MLHS represents a list of values being destructured on the left-hand side # of a multiple assignment. @@ -6991,6 +5563,10 @@ def initialize(parts:, comma: false, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_mlhs(self) + end + def child_nodes parts end @@ -7005,27 +5581,6 @@ def format(q) q.seplist(parts) { |part| q.format(part) } q.text(",") if comma end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("mlhs") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :mlhs, - parts: parts, - comma: comma, - loc: location, - cmts: comments - }.to_json(*opts) - end end # MLHSParen represents parentheses being used to destruct values in a multiple @@ -7046,6 +5601,10 @@ def initialize(contents:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_mlhs_paren(self) + end + def child_nodes [contents] end @@ -7072,26 +5631,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("mlhs_paren") - - q.breakable - q.pp(contents) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :mlhs_paren, - cnts: contents, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ModuleDeclaration represents defining a module using the +module+ keyword. @@ -7116,6 +5655,10 @@ def initialize(constant:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_module(self) + end + def child_nodes [constant, bodystmt] end @@ -7159,30 +5702,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("module") - - q.breakable - q.pp(constant) - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :module, - constant: constant, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # MRHS represents the values that are being assigned on the right-hand side of @@ -7203,6 +5722,10 @@ def initialize(parts:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_mrhs(self) + end + def child_nodes parts end @@ -7216,23 +5739,6 @@ def deconstruct_keys(keys) def format(q) q.seplist(parts) { |part| q.format(part) } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("mrhs") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :mrhs, parts: parts, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Next represents using the +next+ keyword. @@ -7265,6 +5771,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_next(self) + end + def child_nodes [arguments] end @@ -7278,23 +5788,6 @@ def deconstruct_keys(keys) def format(q) FlowControlFormatter.new("next", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("next") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :next, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Op represents an operator literal in the source. @@ -7315,6 +5808,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_op(self) + end + def child_nodes [] end @@ -7328,21 +5825,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("op") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :op, value: value, loc: location, cmts: comments }.to_json(*opts) - end end # OpAssign represents assigning a value to a variable or constant using an @@ -7372,6 +5854,10 @@ def initialize(target:, operator:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_opassign(self) + end + def child_nodes [target, operator, value] end @@ -7393,39 +5879,24 @@ def format(q) q.format(target) q.text(" ") q.format(operator) - q.indent do - q.breakable + + if skip_indent? + q.text(" ") q.format(value) + else + q.indent do + q.breakable + q.format(value) + end end end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("opassign") - - q.breakable - q.pp(target) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end + private - def to_json(*opts) - { - type: :opassign, - target: target, - op: operator, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) + def skip_indent? + target.comments.empty? && + (target.is_a?(ARefField) || AssignFormatting.skip_indent?(value)) end end @@ -7622,6 +6093,10 @@ def empty? keywords.empty? && !keyword_rest && !block end + def accept(visitor) + visitor.visit_params(self) + end + def child_nodes [ *requireds, @@ -7683,85 +6158,6 @@ def format(q) q.nest(0, &contents) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("params") - - if requireds.any? - q.breakable - q.group(2, "(", ")") { q.seplist(requireds) { |name| q.pp(name) } } - end - - if optionals.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(optionals) do |(name, default)| - q.pp(name) - q.text("=") - q.group(2) do - q.breakable("") - q.pp(default) - end - end - end - end - - if rest - q.breakable - q.pp(rest) - end - - if posts.any? - q.breakable - q.group(2, "(", ")") { q.seplist(posts) { |value| q.pp(value) } } - end - - if keywords.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(keywords) do |(name, default)| - q.pp(name) - - if default - q.text("=") - q.group(2) do - q.breakable("") - q.pp(default) - end - end - end - end - end - - if keyword_rest - q.breakable - q.pp(keyword_rest) - end - - if block - q.breakable - q.pp(block) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :params, - reqs: requireds, - opts: optionals, - rest: rest, - posts: posts, - keywords: keywords, - kwrest: keyword_rest, - block: block, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Paren represents using balanced parentheses in a couple places in a Ruby @@ -7787,6 +6183,10 @@ def initialize(lparen:, contents:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_paren(self) + end + def child_nodes [lparen, contents] end @@ -7817,27 +6217,6 @@ def format(q) q.text(")") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("paren") - - q.breakable - q.pp(contents) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :paren, - lparen: lparen, - cnts: contents, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Period represents the use of the +.+ operator. It is usually found in method @@ -7855,6 +6234,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_period(self) + end + def child_nodes [] end @@ -7868,23 +6251,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("period") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :period, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Program represents the overall syntax tree. @@ -7901,6 +6267,10 @@ def initialize(statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_program(self) + end + def child_nodes [statements] end @@ -7919,27 +6289,6 @@ def format(q) # replicate the text exactly so we will just let it be. q.breakable(force: true) unless statements.body.last.is_a?(EndContent) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("program") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :program, - stmts: statements, - comments: comments, - loc: location, - cmts: comments - }.to_json(*opts) - end end # QSymbols represents a symbol literal array without interpolation. @@ -7963,6 +6312,10 @@ def initialize(beginning:, elements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_qsymbols(self) + end + def child_nodes [] end @@ -7996,26 +6349,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("qsymbols") - - q.breakable - q.group(2, "(", ")") { q.seplist(elements) { |element| q.pp(element) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :qsymbols, - elems: elements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # QSymbolsBeg represents the beginning of a symbol literal array. @@ -8033,6 +6366,29 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_qsymbols_beg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("qsymbols_beg") + + q.breakable + q.pp(value) + end + end end # QWords represents a string literal array without interpolation. @@ -8056,6 +6412,10 @@ def initialize(beginning:, elements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_qwords(self) + end + def child_nodes [] end @@ -8089,24 +6449,7 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("qwords") - - q.breakable - q.group(2, "(", ")") { q.seplist(elements) { |element| q.pp(element) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :qwords, elems: elements, loc: location, cmts: comments }.to_json( - *opts - ) - end - end + end # QWordsBeg represents the beginning of a string literal array. # @@ -8123,6 +6466,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_qwords_beg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # RationalLiteral represents the use of a rational number literal. @@ -8142,6 +6499,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rational(self) + end + def child_nodes [] end @@ -8155,23 +6516,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rational") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :rational, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # RBrace represents the use of a right brace, i.e., +++. @@ -8183,6 +6527,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_rbrace(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # RBracket represents the use of a right bracket, i.e., +]+. @@ -8194,6 +6552,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_rbracket(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # Redo represents the use of the +redo+ keyword. @@ -8213,6 +6585,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_redo(self) + end + def child_nodes [] end @@ -8226,23 +6602,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("redo") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :redo, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # RegexpContent represents the body of a regular expression. @@ -8264,6 +6623,20 @@ def initialize(beginning:, parts:, location:) @parts = parts @location = location end + + def accept(visitor) + visitor.visit_regexp_content(self) + end + + def child_nodes + parts + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { beginning: beginning, parts: parts, location: location } + end end # RegexpBeg represents the start of a regular expression literal. @@ -8283,6 +6656,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_regexp_beg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # RegexpEnd represents the end of a regular expression literal. @@ -8303,6 +6690,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_regexp_end(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # RegexpLiteral represents a regular expression literal. @@ -8331,6 +6732,10 @@ def initialize(beginning:, ending:, parts:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_regexp_literal(self) + end + def child_nodes parts end @@ -8387,28 +6792,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("regexp_literal") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :regexp_literal, - beging: beginning, - ending: ending, - parts: parts, - loc: location, - cmts: comments - }.to_json(*opts) - end - private def include?(pattern) @@ -8454,6 +6837,10 @@ def initialize(exceptions:, variable:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rescue_ex(self) + end + def child_nodes [*exceptions, variable] end @@ -8482,30 +6869,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rescue_ex") - - q.breakable - q.pp(exceptions) - - q.breakable - q.pp(variable) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :rescue_ex, - extns: exceptions, - var: variable, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Rescue represents the use of the rescue keyword inside of a BodyStmt node. @@ -8515,6 +6878,9 @@ def to_json(*opts) # end # class Rescue < Node + # [Kw] the rescue keyword + attr_reader :keyword + # [RescueEx] the exceptions being rescued attr_reader :exception @@ -8528,12 +6894,14 @@ class Rescue < Node attr_reader :comments def initialize( + keyword:, exception:, statements:, consequent:, location:, comments: [] ) + @keyword = keyword @exception = exception @statements = statements @consequent = consequent @@ -8541,31 +6909,38 @@ def initialize( @comments = comments end - def bind_end(end_char) + def bind_end(end_char, end_column) @location = Location.new( start_line: location.start_line, start_char: location.start_char, + start_column: location.start_column, end_line: location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) if consequent - consequent.bind_end(end_char) - statements.bind_end(consequent.location.start_char) + consequent.bind_end(end_char, end_column) + statements.bind_end(consequent.location.start_char, consequent.location.start_column) else - statements.bind_end(end_char) + statements.bind_end(end_char, end_column) end end + def accept(visitor) + visitor.visit_rescue(self) + end + def child_nodes - [exception, statements, consequent] + [keyword, exception, statements, consequent] end alias deconstruct child_nodes def deconstruct_keys(keys) { + keyword: keyword, exception: exception, statements: statements, consequent: consequent, @@ -8576,10 +6951,10 @@ def deconstruct_keys(keys) def format(q) q.group do - q.text("rescue") + q.format(keyword) if exception - q.nest("rescue ".length) { q.format(exception) } + q.nest(keyword.value.length + 1) { q.format(exception) } else q.text(" StandardError") end @@ -8597,38 +6972,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rescue") - - if exception - q.breakable - q.pp(exception) - end - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :rescue, - extn: exception, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # RescueMod represents the use of the modifier form of a +rescue+ clause. @@ -8652,6 +6995,10 @@ def initialize(statement:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rescue_mod(self) + end + def child_nodes [statement, value] end @@ -8682,30 +7029,6 @@ def format(q) q.breakable(force: true) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rescue_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :rescue_mod, - stmt: statement, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # RestParam represents defining a parameter in a method definition that @@ -8726,6 +7049,10 @@ def initialize(name:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rest_param(self) + end + def child_nodes [name] end @@ -8740,23 +7067,6 @@ def format(q) q.text("*") q.format(name) if name end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rest_param") - - q.breakable - q.pp(name) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :rest_param, name: name, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Retry represents the use of the +retry+ keyword. @@ -8776,6 +7086,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_retry(self) + end + def child_nodes [] end @@ -8789,23 +7103,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("retry") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :retry, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Return represents using the +return+ keyword with arguments. @@ -8825,6 +7122,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_return(self) + end + def child_nodes [arguments] end @@ -8838,23 +7139,6 @@ def deconstruct_keys(keys) def format(q) FlowControlFormatter.new("return", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("return") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :return, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Return0 represents the bare +return+ keyword with no arguments. @@ -8874,6 +7158,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_return0(self) + end + def child_nodes [] end @@ -8887,23 +7175,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("return0") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :return0, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # RParen represents the use of a right parenthesis, i.e., +)+. @@ -8915,6 +7186,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_rparen(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # SClass represents a block of statements that should be evaluated within the @@ -8941,6 +7226,10 @@ def initialize(target:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_sclass(self) + end + def child_nodes [target, bodystmt] end @@ -8966,30 +7255,6 @@ def format(q) q.breakable(force: true) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("sclass") - - q.breakable - q.pp(target) - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :sclass, - target: target, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Everything that has a block of code inside of it has a list of statements. @@ -9016,13 +7281,15 @@ def initialize(parser, body:, location:, comments: []) @comments = comments end - def bind(start_char, end_char) + def bind(start_char, start_column, end_char, end_column) @location = Location.new( start_line: location.start_line, start_char: start_char, + start_column: start_column, end_line: location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) if body[0].is_a?(VoidStmt) @@ -9031,8 +7298,10 @@ def bind(start_char, end_char) Location.new( start_line: location.start_line, start_char: start_char, + start_column: start_column, end_line: location.end_line, - end_char: start_char + end_char: start_char, + end_column: end_column ) body[0] = VoidStmt.new(location: location) @@ -9041,13 +7310,15 @@ def bind(start_char, end_char) attach_comments(start_char, end_char) end - def bind_end(end_char) + def bind_end(end_char, end_column) @location = Location.new( start_line: location.start_line, start_char: location.start_char, + start_column: location.start_column, end_line: location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) end @@ -9057,6 +7328,10 @@ def empty? end end + def accept(visitor) + visitor.visit_statements(self) + end + def child_nodes body end @@ -9118,23 +7393,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("statements") - - q.breakable - q.seplist(body) { |statement| q.pp(statement) } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :statements, body: body, loc: location, cmts: comments }.to_json( - *opts - ) - end - private # As efficiently as possible, gather up all of the comments that have been @@ -9183,6 +7441,20 @@ def initialize(parts:, location:) @parts = parts @location = location end + + def accept(visitor) + visitor.visit_string_content(self) + end + + def child_nodes + parts + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { parts: parts, location: location } + end end # StringConcat represents concatenating two strings together using a backward @@ -9208,6 +7480,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_string_concat(self) + end + def child_nodes [left, right] end @@ -9228,30 +7504,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("string_concat") - - q.breakable - q.pp(left) - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :string_concat, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # StringDVar represents shorthand interpolation of a variable into a string. @@ -9273,6 +7525,10 @@ def initialize(variable:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_string_dvar(self) + end + def child_nodes [variable] end @@ -9288,26 +7544,6 @@ def format(q) q.format(variable) q.text("}") end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("string_dvar") - - q.breakable - q.pp(variable) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :string_dvar, - var: variable, - loc: location, - cmts: comments - }.to_json(*opts) - end end # StringEmbExpr represents interpolated content. It can be contained within a @@ -9329,6 +7565,10 @@ def initialize(statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_string_embexpr(self) + end + def child_nodes [statements] end @@ -9359,26 +7599,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("string_embexpr") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :string_embexpr, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # StringLiteral represents a string literal. @@ -9403,6 +7623,10 @@ def initialize(parts:, quote:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_string_literal(self) + end + def child_nodes parts end @@ -9442,27 +7666,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("string_literal") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :string_literal, - parts: parts, - quote: quote, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Super represents using the +super+ keyword with arguments. It can optionally @@ -9483,6 +7686,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_super(self) + end + def child_nodes [arguments] end @@ -9505,23 +7712,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("super") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :super, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # SymBeg represents the beginning of a symbol literal. @@ -9548,6 +7738,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_symbeg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # SymbolContent represents symbol contents and is always the child of a @@ -9564,6 +7768,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_symbol_content(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # SymbolLiteral represents a symbol in the system with no interpolation @@ -9585,6 +7803,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_symbol_literal(self) + end + def child_nodes [value] end @@ -9599,26 +7821,6 @@ def format(q) q.text(":") q.format(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("symbol_literal") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :symbol_literal, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Symbols represents a symbol array literal with interpolation. @@ -9642,6 +7844,10 @@ def initialize(beginning:, elements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_symbols(self) + end + def child_nodes [] end @@ -9675,26 +7881,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("symbols") - - q.breakable - q.group(2, "(", ")") { q.seplist(elements) { |element| q.pp(element) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :symbols, - elems: elements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # SymbolsBeg represents the start of a symbol array literal with @@ -9713,6 +7899,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_symbols_beg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # TLambda represents the beginning of a lambda literal. @@ -9728,6 +7928,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_tlambda(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # TLamBeg represents the beginning of the body of a lambda literal using @@ -9744,6 +7958,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_tlambeg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # TopConstField is always the child node of some kind of assignment. It @@ -9765,6 +7993,10 @@ def initialize(constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_top_const_field(self) + end + def child_nodes [constant] end @@ -9779,26 +8011,6 @@ def format(q) q.text("::") q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("top_const_field") - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :top_const_field, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # TopConstRef is very similar to TopConstField except that it is not involved @@ -9819,6 +8031,10 @@ def initialize(constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_top_const_ref(self) + end + def child_nodes [constant] end @@ -9833,26 +8049,6 @@ def format(q) q.text("::") q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("top_const_ref") - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :top_const_ref, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # TStringBeg represents the beginning of a string literal. @@ -9873,6 +8069,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_tstring_beg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # TStringContent represents plain characters inside of an entity that accepts @@ -9900,6 +8110,10 @@ def match?(pattern) value.match?(pattern) end + def accept(visitor) + visitor.visit_tstring_content(self) + end + def child_nodes [] end @@ -9913,26 +8127,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("tstring_content") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :tstring_content, - value: value.force_encoding("UTF-8"), - loc: location, - cmts: comments - }.to_json(*opts) - end end # TStringEnd represents the end of a string literal. @@ -9953,6 +8147,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_tstring_end(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # Not represents the unary +not+ method being called on an expression. @@ -9976,6 +8184,10 @@ def initialize(statement:, parentheses:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_not(self) + end + def child_nodes [statement] end @@ -9996,27 +8208,6 @@ def format(q) q.format(statement) q.text(")") if parentheses end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("not") - - q.breakable - q.pp(statement) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :not, - value: statement, - paren: parentheses, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Unary represents a unary method being called on an expression, as in +!+ or @@ -10041,6 +8232,10 @@ def initialize(operator:, statement:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_unary(self) + end + def child_nodes [statement] end @@ -10060,30 +8255,6 @@ def format(q) q.text(operator) q.format(statement) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("unary") - - q.breakable - q.pp(operator) - - q.breakable - q.pp(statement) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :unary, - op: operator, - value: statement, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Undef represents the use of the +undef+ keyword. @@ -10124,6 +8295,10 @@ def initialize(symbols:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_undef(self) + end + def child_nodes symbols end @@ -10145,23 +8320,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("undef") - - q.breakable - q.group(2, "(", ")") { q.seplist(symbols) { |symbol| q.pp(symbol) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :undef, syms: symbols, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Unless represents the first clause in an +unless+ chain. @@ -10196,6 +8354,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_unless(self) + end + def child_nodes [predicate, statements, consequent] end @@ -10215,36 +8377,6 @@ def deconstruct_keys(keys) def format(q) ConditionalFormatter.new("unless", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("unless") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :unless, - pred: predicate, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # UnlessMod represents the modifier form of an +unless+ statement. @@ -10268,6 +8400,10 @@ def initialize(statement:, predicate:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_unless_mod(self) + end + def child_nodes [statement, predicate] end @@ -10286,30 +8422,6 @@ def deconstruct_keys(keys) def format(q) ConditionalModFormatter.new("unless", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("unless_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(predicate) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :unless_mod, - stmt: statement, - pred: predicate, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Formats an Until, UntilMod, While, or WhileMod node. @@ -10383,6 +8495,10 @@ def initialize(predicate:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_until(self) + end + def child_nodes [predicate, statements] end @@ -10412,30 +8528,6 @@ def format(q) LoopFormatter.new("until", self, statements).format(q) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("until") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :until, - pred: predicate, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # UntilMod represents the modifier form of a +until+ loop. @@ -10459,6 +8551,10 @@ def initialize(statement:, predicate:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_until_mod(self) + end + def child_nodes [statement, predicate] end @@ -10493,34 +8589,10 @@ def format(q) q.format(statement) q.text(" until ") q.format(predicate) - else - LoopFormatter.new("until", self, statement).format(q) - end - end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("until_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(predicate) - - q.pp(Comment::List.new(comments)) + else + LoopFormatter.new("until", self, statement).format(q) end end - - def to_json(*opts) - { - type: :until_mod, - stmt: statement, - pred: predicate, - loc: location, - cmts: comments - }.to_json(*opts) - end end # VarAlias represents when you're using the +alias+ keyword with global @@ -10545,6 +8617,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_var_alias(self) + end + def child_nodes [left, right] end @@ -10563,30 +8639,6 @@ def format(q) q.text(" ") q.format(right) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("var_alias") - - q.breakable - q.pp(left) - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :var_alias, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # VarField represents a variable that is being assigned a value. As such, it @@ -10608,6 +8660,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_var_field(self) + end + def child_nodes [value] end @@ -10621,23 +8677,6 @@ def deconstruct_keys(keys) def format(q) q.format(value) if value end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("var_field") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :var_field, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # VarRef represents a variable reference. @@ -10661,6 +8700,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_var_ref(self) + end + def child_nodes [value] end @@ -10674,23 +8717,6 @@ def deconstruct_keys(keys) def format(q) q.format(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("var_ref") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :var_ref, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # PinnedVarRef represents a pinned variable reference within a pattern @@ -10715,6 +8741,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_pinned_var_ref(self) + end + def child_nodes [value] end @@ -10731,26 +8761,6 @@ def format(q) q.format(value) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("pinned_var_ref") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :pinned_var_ref, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # VCall represent any plain named object with Ruby that could be either a @@ -10771,6 +8781,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_vcall(self) + end + def child_nodes [value] end @@ -10784,23 +8798,6 @@ def deconstruct_keys(keys) def format(q) q.format(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("vcall") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :vcall, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # VoidStmt represents an empty lexical block of code. @@ -10819,6 +8816,10 @@ def initialize(location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_void_stmt(self) + end + def child_nodes [] end @@ -10831,17 +8832,6 @@ def deconstruct_keys(keys) def format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("void_stmt") - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :void_stmt, loc: location, cmts: comments }.to_json(*opts) - end end # When represents a +when+ clause in a +case+ chain. @@ -10877,6 +8867,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_when(self) + end + def child_nodes [arguments, statements, consequent] end @@ -10930,36 +8924,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("when") - - q.breakable - q.pp(arguments) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :when, - args: arguments, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # While represents a +while+ loop. @@ -10984,6 +8948,10 @@ def initialize(predicate:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_while(self) + end + def child_nodes [predicate, statements] end @@ -11013,30 +8981,6 @@ def format(q) LoopFormatter.new("while", self, statements).format(q) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("while") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :while, - pred: predicate, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # WhileMod represents the modifier form of a +while+ loop. @@ -11060,6 +9004,10 @@ def initialize(statement:, predicate:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_while_mod(self) + end + def child_nodes [statement, predicate] end @@ -11098,30 +9046,6 @@ def format(q) LoopFormatter.new("while", self, statement).format(q) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("while_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(predicate) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :while_mod, - stmt: statement, - pred: predicate, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Word represents an element within a special array literal that accepts @@ -11149,6 +9073,10 @@ def match?(pattern) parts.any? { |part| part.is_a?(TStringContent) && part.match?(pattern) } end + def accept(visitor) + visitor.visit_word(self) + end + def child_nodes parts end @@ -11162,23 +9090,6 @@ def deconstruct_keys(keys) def format(q) q.format_each(parts) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("word") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :word, parts: parts, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Words represents a string literal array with interpolation. @@ -11202,6 +9113,10 @@ def initialize(beginning:, elements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_words(self) + end + def child_nodes [] end @@ -11235,23 +9150,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("words") - - q.breakable - q.group(2, "(", ")") { q.seplist(elements) { |element| q.pp(element) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :words, elems: elements, loc: location, cmts: comments }.to_json( - *opts - ) - end end # WordsBeg represents the beginning of a string literal array with @@ -11270,6 +9168,20 @@ def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_words_beg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end end # XString represents the contents of an XStringLiteral. @@ -11285,6 +9197,20 @@ def initialize(parts:, location:) @parts = parts @location = location end + + def accept(visitor) + visitor.visit_xstring(self) + end + + def child_nodes + parts + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { parts: parts, location: location } + end end # XStringLiteral represents a string that gets executed. @@ -11305,6 +9231,10 @@ def initialize(parts:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_xstring_literal(self) + end + def child_nodes parts end @@ -11320,26 +9250,6 @@ def format(q) q.format_each(parts) q.text("`") end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("xstring_literal") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :xstring_literal, - parts: parts, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Yield represents using the +yield+ keyword with arguments. @@ -11359,6 +9269,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_yield(self) + end + def child_nodes [arguments] end @@ -11386,23 +9300,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("yield") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :yield, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Yield0 represents the bare +yield+ keyword with no arguments. @@ -11422,6 +9319,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_yield0(self) + end + def child_nodes [] end @@ -11435,23 +9336,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("yield0") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :yield0, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ZSuper represents the bare +super+ keyword with no arguments. @@ -11471,6 +9355,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_zsuper(self) + end + def child_nodes [] end @@ -11484,22 +9372,5 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("zsuper") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { type: :zsuper, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end end diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index 33eeef8b..60923b57 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -163,6 +163,13 @@ def char_pos line_counts[lineno - 1][column] end + # This represents the current column we're in relative to the beginning of + # the current line. + def current_column + line = line_counts[lineno - 1] + line[column].to_i - line.start + end + # As we build up a list of tokens, we'll periodically need to go backwards # and find the ones that we've already hit in order to determine the # location information for nodes that use them. For example, if you have a @@ -251,10 +258,13 @@ def find_next_statement_start(position) def on_BEGIN(statements) lbrace = find_token(LBrace) rbrace = find_token(RBrace) + start_char = find_next_statement_start(lbrace.location.end_char) statements.bind( - find_next_statement_start(lbrace.location.end_char), - rbrace.location.start_char + start_char, + start_char - line_counts[lbrace.location.start_line - 1].start, + rbrace.location.start_char, + rbrace.location.start_column, ) keyword = find_token(Kw, "BEGIN") @@ -271,7 +281,7 @@ def on_BEGIN(statements) def on_CHAR(value) CHAR.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -280,10 +290,13 @@ def on_CHAR(value) def on_END(statements) lbrace = find_token(LBrace) rbrace = find_token(RBrace) + start_char = find_next_statement_start(lbrace.location.end_char) statements.bind( - find_next_statement_start(lbrace.location.end_char), - rbrace.location.start_char + start_char, + start_char - line_counts[lbrace.location.start_line - 1].start, + rbrace.location.start_char, + rbrace.location.start_column ) keyword = find_token(Kw, "END") @@ -301,7 +314,7 @@ def on___end__(value) @__end__ = EndContent.new( value: source[(char_pos + value.length)..-1], - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -465,7 +478,7 @@ def on_args_forward # :call-seq: # on_args_new: () -> Args def on_args_new - Args.new(parts: [], location: Location.fixed(line: lineno, char: char_pos)) + Args.new(parts: [], location: Location.fixed(line: lineno, column: current_column, char: char_pos)) end # :call-seq: @@ -551,7 +564,7 @@ def on_assoc_splat(value) def on_backref(value) Backref.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -561,7 +574,7 @@ def on_backtick(value) node = Backtick.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -592,15 +605,20 @@ def on_begin(bodystmt) PinnedBegin.new(statement: bodystmt, location: location) else keyword = find_token(Kw, "begin") - end_char = + end_location = if bodystmt.rescue_clause || bodystmt.ensure_clause || bodystmt.else_clause - bodystmt.location.end_char + bodystmt.location else - find_token(Kw, "end").location.end_char + find_token(Kw, "end").location end - bodystmt.bind(keyword.location.end_char, end_char) + bodystmt.bind( + keyword.location.end_char, + keyword.location.end_column, + end_location.end_char, + end_location.end_column + ) location = keyword.location.to(bodystmt.location) Begin.new(bodystmt: bodystmt, location: location) @@ -682,9 +700,10 @@ def on_bodystmt(statements, rescue_clause, else_clause, ensure_clause) BodyStmt.new( statements: statements, rescue_clause: rescue_clause, + else_keyword: else_clause && find_token(Kw, "else"), else_clause: else_clause, ensure_clause: ensure_clause, - location: Location.fixed(line: lineno, char: char_pos) + location: Location.fixed(line: lineno, char: char_pos, column: current_column) ) end @@ -696,18 +715,24 @@ def on_bodystmt(statements, rescue_clause, else_clause, ensure_clause) def on_brace_block(block_var, statements) lbrace = find_token(LBrace) rbrace = find_token(RBrace) + location = (block_var || lbrace).location + start_char = find_next_statement_start(location.end_char) statements.bind( - find_next_statement_start((block_var || lbrace).location.end_char), - rbrace.location.start_char + start_char, + start_char - line_counts[location.start_line - 1].start, + rbrace.location.start_char, + rbrace.location.start_column ) location = Location.new( start_line: lbrace.location.start_line, start_char: lbrace.location.start_char, + start_column: lbrace.location.start_column, end_line: [rbrace.location.end_line, statements.location.end_line].max, - end_char: rbrace.location.end_char + end_char: rbrace.location.end_char, + end_column: rbrace.location.end_column ) BraceBlock.new( @@ -781,10 +806,14 @@ def on_case(value, consequent) def on_class(constant, superclass, bodystmt) beginning = find_token(Kw, "class") ending = find_token(Kw, "end") + location = (superclass || constant).location + start_char = find_next_statement_start(location.end_char) bodystmt.bind( - find_next_statement_start((superclass || constant).location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) ClassDeclaration.new( @@ -801,7 +830,7 @@ def on_comma(value) node = Comma.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -846,7 +875,7 @@ def on_comment(value) value: value.chomp, inline: value.strip != lines[line - 1].strip, location: - Location.token(line: line, char: char_pos, size: value.size - 1) + Location.token(line: line, char: char_pos, column: current_column, size: value.size - 1) ) @comments << comment @@ -858,7 +887,7 @@ def on_comment(value) def on_const(value) Const.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -893,7 +922,7 @@ def on_const_ref(constant) def on_cvar(value) CVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -917,12 +946,15 @@ def on_def(name, params, bodystmt) # location information if params.is_a?(Params) && params.empty? end_char = name.location.end_char + end_column = name.location.end_column location = Location.new( start_line: params.location.start_line, start_char: end_char, + start_column: end_column, end_line: params.location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) params = Params.new(location: location) @@ -932,9 +964,13 @@ def on_def(name, params, bodystmt) if ending tokens.delete(ending) + start_char = find_next_statement_start(params.location.end_char) + bodystmt.bind( - find_next_statement_start(params.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[params.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) Def.new( @@ -992,12 +1028,15 @@ def on_defs(target, operator, name, params, bodystmt) # location information if params.is_a?(Params) && params.empty? end_char = name.location.end_char + end_column = name.location.end_column location = Location.new( start_line: params.location.start_line, start_char: end_char, + start_column: end_column, end_line: params.location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) params = Params.new(location: location) @@ -1008,9 +1047,13 @@ def on_defs(target, operator, name, params, bodystmt) if ending tokens.delete(ending) + start_char = find_next_statement_start(params.location.end_char) + bodystmt.bind( - find_next_statement_start(params.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[params.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) Defs.new( @@ -1042,10 +1085,14 @@ def on_defs(target, operator, name, params, bodystmt) def on_do_block(block_var, bodystmt) beginning = find_token(Kw, "do") ending = find_token(Kw, "end") + location = (block_var || beginning).location + start_char = find_next_statement_start(location.end_char) bodystmt.bind( - find_next_statement_start((block_var || beginning).location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) DoBlock.new( @@ -1115,7 +1162,7 @@ def on_dyna_symbol(string_content) # :call-seq: # on_else: (Statements statements) -> Else def on_else(statements) - beginning = find_token(Kw, "else") + keyword = find_token(Kw, "else") # else can either end with an end keyword (in which case we'll want to # consume that event) or it can end with an ensure keyword (in which case @@ -1127,13 +1174,19 @@ def on_else(statements) node = tokens[index] ending = node.value == "end" ? tokens.delete_at(index) : node - # ending = node + start_char = find_next_statement_start(keyword.location.end_char) - statements.bind(beginning.location.end_char, ending.location.start_char) + statements.bind( + start_char, + start_char - line_counts[keyword.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column + ) Else.new( + keyword: keyword, statements: statements, - location: beginning.location.to(ending.location) + location: keyword.location.to(ending.location) ) end @@ -1147,7 +1200,12 @@ def on_elsif(predicate, statements, consequent) beginning = find_token(Kw, "elsif") ending = consequent || find_token(Kw, "end") - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) Elsif.new( predicate: predicate, @@ -1170,7 +1228,7 @@ def on_embdoc_beg(value) @embdoc = EmbDoc.new( value: value, - location: Location.fixed(line: lineno, char: char_pos) + location: Location.fixed(line: lineno, column: current_column, char: char_pos) ) end @@ -1185,8 +1243,10 @@ def on_embdoc_end(value) Location.new( start_line: location.start_line, start_char: location.start_char, + start_column: location.start_column, end_line: lineno, - end_char: char_pos + value.length - 1 + end_char: char_pos + value.length - 1, + end_column: current_column + value.length - 1 ) ) @@ -1202,7 +1262,7 @@ def on_embexpr_beg(value) node = EmbExprBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1215,7 +1275,7 @@ def on_embexpr_end(value) node = EmbExprEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1228,7 +1288,7 @@ def on_embvar(value) node = EmbVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1243,9 +1303,12 @@ def on_ensure(statements) # We don't want to consume the :@kw event, because that would break # def..ensure..end chains. ending = find_token(Kw, "end", consume: false) + start_char = find_next_statement_start(keyword.location.end_char) statements.bind( - find_next_statement_start(keyword.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[keyword.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) Ensure.new( @@ -1292,7 +1355,7 @@ def on_field(parent, operator, name) def on_float(value) FloatLiteral.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1337,7 +1400,9 @@ def on_for(index, collection, statements) statements.bind( (keyword || collection).location.end_char, - ending.location.start_char + (keyword || collection).location.end_column, + ending.location.start_char, + ending.location.start_column ) if index.is_a?(MLHS) @@ -1358,7 +1423,7 @@ def on_for(index, collection, statements) def on_gvar(value) GVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1379,7 +1444,7 @@ def on_hash(assocs) # on_heredoc_beg: (String value) -> HeredocBeg def on_heredoc_beg(value) location = - Location.token(line: lineno, char: char_pos, size: value.size + 1) + Location.token(line: lineno, char: char_pos, column: current_column, size: value.size + 1) # Here we're going to artificially create an extra node type so that if # there are comments after the declaration of a heredoc, they get printed. @@ -1415,8 +1480,10 @@ def on_heredoc_end(value) Location.new( start_line: heredoc.location.start_line, start_char: heredoc.location.start_char, + start_column: heredoc.location.start_column, end_line: lineno, - end_char: char_pos + end_char: char_pos, + end_column: current_column, ) ) end @@ -1443,7 +1510,7 @@ def on_hshptn(constant, keywords, keyword_rest) def on_ident(value) Ident.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1457,7 +1524,12 @@ def on_if(predicate, statements, consequent) beginning = find_token(Kw, "if") ending = consequent || find_token(Kw, "end") - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) If.new( predicate: predicate, @@ -1503,7 +1575,7 @@ def on_if_mod(predicate, statement) def on_imaginary(value) Imaginary.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1527,9 +1599,12 @@ def on_in(pattern, statements, consequent) statements_start = token end + start_char = find_next_statement_start(statements_start.location.end_char) statements.bind( - find_next_statement_start(statements_start.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[statements_start.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) In.new( @@ -1545,7 +1620,7 @@ def on_in(pattern, statements, consequent) def on_int(value) Int.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1554,7 +1629,7 @@ def on_int(value) def on_ivar(value) IVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1564,7 +1639,7 @@ def on_kw(value) node = Kw.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1585,7 +1660,7 @@ def on_kwrest_param(name) def on_label(value) Label.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1595,7 +1670,7 @@ def on_label_end(value) node = LabelEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1621,7 +1696,12 @@ def on_lambda(params, statements) closing = find_token(Kw, "end") end - statements.bind(opening.location.end_char, closing.location.start_char) + statements.bind( + opening.location.end_char, + opening.location.end_column, + closing.location.start_char, + closing.location.start_column + ) Lambda.new( params: params, @@ -1636,7 +1716,7 @@ def on_lbrace(value) node = LBrace.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1649,7 +1729,7 @@ def on_lbracket(value) node = LBracket.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1662,7 +1742,7 @@ def on_lparen(value) node = LParen.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1761,7 +1841,7 @@ def on_mlhs_add_star(mlhs, part) # :call-seq: # on_mlhs_new: () -> MLHS def on_mlhs_new - MLHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos)) + MLHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos, column: current_column)) end # :call-seq: @@ -1787,10 +1867,13 @@ def on_mlhs_paren(contents) def on_module(constant, bodystmt) beginning = find_token(Kw, "module") ending = find_token(Kw, "end") + start_char = find_next_statement_start(constant.location.end_char) bodystmt.bind( - find_next_statement_start(constant.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[constant.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) ModuleDeclaration.new( @@ -1803,7 +1886,7 @@ def on_module(constant, bodystmt) # :call-seq: # on_mrhs_new: () -> MRHS def on_mrhs_new - MRHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos)) + MRHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos, column: current_column)) end # :call-seq: @@ -1872,7 +1955,7 @@ def on_op(value) node = Op.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1931,7 +2014,7 @@ def on_params( if parts.any? parts[0].location.to(parts[-1].location) else - Location.fixed(line: lineno, char: char_pos) + Location.fixed(line: lineno, char: char_pos, column: current_column) end Params.new( @@ -1954,12 +2037,15 @@ def on_paren(contents) if contents && contents.is_a?(Params) location = contents.location + start_char = find_next_statement_start(lparen.location.end_char) location = Location.new( start_line: location.start_line, - start_char: find_next_statement_start(lparen.location.end_char), + start_char: start_char, + start_column: start_char - line_counts[lparen.location.start_line - 1].start, end_line: location.end_line, - end_char: rparen.location.start_char + end_char: rparen.location.start_char, + end_column: rparen.location.start_column ) contents = @@ -1997,23 +2083,26 @@ def on_parse_error(error, *) def on_period(value) Period.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end # :call-seq: # on_program: (Statements statements) -> Program def on_program(statements) + last_column = source.length - line_counts[lines.length - 1].start location = Location.new( start_line: 1, start_char: 0, + start_column: 0, end_line: lines.length, - end_char: source.length + end_char: source.length, + end_column: last_column ) statements.body << @__end__ if @__end__ - statements.bind(0, source.length) + statements.bind(0, 0, source.length, last_column) program = Program.new(statements: statements, location: location) attach_comments(program, @comments) @@ -2126,7 +2215,7 @@ def on_qsymbols_beg(value) node = QSymbolsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2161,7 +2250,7 @@ def on_qwords_beg(value) node = QWordsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2181,7 +2270,7 @@ def on_qwords_new def on_rational(value) RationalLiteral.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -2191,7 +2280,7 @@ def on_rbrace(value) node = RBrace.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2204,7 +2293,7 @@ def on_rbracket(value) node = RBracket.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2238,7 +2327,7 @@ def on_regexp_beg(value) node = RegexpBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2250,7 +2339,7 @@ def on_regexp_beg(value) def on_regexp_end(value) RegexpEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -2292,9 +2381,12 @@ def on_rescue(exceptions, variable, statements, consequent) exceptions = exceptions[0] if exceptions.is_a?(Array) last_node = variable || exceptions || keyword + start_char = find_next_statement_start(last_node.location.end_char) statements.bind( - find_next_statement_start(last_node.location.end_char), - char_pos + start_char, + start_char - line_counts[last_node.location.start_line - 1].start, + char_pos, + current_column ) # We add an additional inner node here that ripper doesn't provide so that @@ -2309,13 +2401,16 @@ def on_rescue(exceptions, variable, statements, consequent) Location.new( start_line: keyword.location.start_line, start_char: keyword.location.end_char + 1, + start_column: keyword.location.end_column + 1, end_line: last_node.location.end_line, - end_char: last_node.location.end_char + end_char: last_node.location.end_char, + end_column: last_node.location.end_column ) ) end Rescue.new( + keyword: keyword, exception: rescue_ex, statements: statements, consequent: consequent, @@ -2323,8 +2418,10 @@ def on_rescue(exceptions, variable, statements, consequent) Location.new( start_line: keyword.location.start_line, start_char: keyword.location.start_char, + start_column: keyword.location.start_column, end_line: lineno, - end_char: char_pos + end_char: char_pos, + end_column: current_column ) ) end @@ -2383,7 +2480,7 @@ def on_rparen(value) node = RParen.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2395,10 +2492,13 @@ def on_rparen(value) def on_sclass(target, bodystmt) beginning = find_token(Kw, "class") ending = find_token(Kw, "end") + start_char = find_next_statement_start(target.location.end_char) bodystmt.bind( - find_next_statement_start(target.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[target.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) SClass.new( @@ -2437,7 +2537,7 @@ def on_stmts_new Statements.new( self, body: [], - location: Location.fixed(line: lineno, char: char_pos) + location: Location.fixed(line: lineno, char: char_pos, column: current_column) ) end @@ -2471,7 +2571,7 @@ def on_string_concat(left, right) def on_string_content StringContent.new( parts: [], - location: Location.fixed(line: lineno, char: char_pos) + location: Location.fixed(line: lineno, char: char_pos, column: current_column) ) end @@ -2494,18 +2594,22 @@ def on_string_embexpr(statements) statements.bind( embexpr_beg.location.end_char, - embexpr_end.location.start_char + embexpr_beg.location.end_column, + embexpr_end.location.start_char, + embexpr_end.location.start_column ) location = Location.new( start_line: embexpr_beg.location.start_line, start_char: embexpr_beg.location.start_char, + start_column: embexpr_beg.location.start_column, end_line: [ embexpr_end.location.end_line, statements.location.end_line ].max, - end_char: embexpr_end.location.end_char + end_char: embexpr_end.location.end_char, + end_column: embexpr_end.location.end_column ) StringEmbExpr.new(statements: statements, location: location) @@ -2533,11 +2637,13 @@ def on_string_literal(string) Location.new( start_line: tstring_beg.location.start_line, start_char: tstring_beg.location.start_char, + start_column: tstring_beg.location.start_column, end_line: [ tstring_end.location.end_line, string.location.end_line ].max, - end_char: tstring_end.location.end_char + end_char: tstring_end.location.end_char, + end_column: tstring_end.location.end_column ) StringLiteral.new( @@ -2566,7 +2672,7 @@ def on_symbeg(value) node = SymBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2620,7 +2726,7 @@ def on_symbols_beg(value) node = SymbolsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2645,7 +2751,7 @@ def on_tlambda(value) node = TLambda.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2658,7 +2764,7 @@ def on_tlambeg(value) node = TLamBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2693,7 +2799,7 @@ def on_tstring_beg(value) node = TStringBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2705,7 +2811,7 @@ def on_tstring_beg(value) def on_tstring_content(value) TStringContent.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -2715,7 +2821,7 @@ def on_tstring_end(value) node = TStringEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2789,7 +2895,12 @@ def on_unless(predicate, statements, consequent) beginning = find_token(Kw, "unless") ending = consequent || find_token(Kw, "end") - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) Unless.new( predicate: predicate, @@ -2826,7 +2937,12 @@ def on_until(predicate, statements) end # Update the Statements location information - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) Until.new( predicate: predicate, @@ -2870,7 +2986,7 @@ def on_var_field(value) else # You can hit this pattern if you're assigning to a splat using # pattern matching syntax in Ruby 2.7+ - Location.fixed(line: lineno, char: char_pos) + Location.fixed(line: lineno, char: char_pos, column: current_column) end VarField.new(value: value, location: location) @@ -2898,7 +3014,7 @@ def on_vcall(ident) # :call-seq: # on_void_stmt: () -> VoidStmt def on_void_stmt - VoidStmt.new(location: Location.fixed(line: lineno, char: char_pos)) + VoidStmt.new(location: Location.fixed(line: lineno, char: char_pos, column: current_column)) end # :call-seq: @@ -2917,9 +3033,13 @@ def on_when(arguments, statements, consequent) statements_start = token end + start_char = find_next_statement_start(statements_start.location.end_char) + statements.bind( - find_next_statement_start(statements_start.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[statements_start.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) When.new( @@ -2945,7 +3065,12 @@ def on_while(predicate, statements) end # Update the Statements location information - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) While.new( predicate: predicate, @@ -2981,7 +3106,7 @@ def on_word_add(word, part) # :call-seq: # on_word_new: () -> Word def on_word_new - Word.new(parts: [], location: Location.fixed(line: lineno, char: char_pos)) + Word.new(parts: [], location: Location.fixed(line: lineno, char: char_pos, column: current_column)) end # :call-seq: @@ -3000,7 +3125,7 @@ def on_words_beg(value) node = WordsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node diff --git a/lib/syntax_tree/version.rb b/lib/syntax_tree/version.rb index fde7d633..f3bc7b04 100644 --- a/lib/syntax_tree/version.rb +++ b/lib/syntax_tree/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SyntaxTree - VERSION = "2.0.1" + VERSION = "2.1.0" end diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb new file mode 100644 index 00000000..63227d03 --- /dev/null +++ b/lib/syntax_tree/visitor.rb @@ -0,0 +1,548 @@ +# frozen_string_literal: true + +module SyntaxTree + class Visitor + # This is raised when you use the Visitor.visit_method method and it fails. + # It is correctable to through DidYouMean. + class VisitMethodError < StandardError + attr_reader :visit_method + + def initialize(visit_method) + @visit_method = visit_method + super("Invalid visit method: #{visit_method}") + end + end + + # This class is used by DidYouMean to offer corrections to invalid visit + # method names. + class VisitMethodChecker + attr_reader :visit_method + + def initialize(error) + @visit_method = error.visit_method + end + + def corrections + @corrections ||= + DidYouMean::SpellChecker.new(dictionary: Visitor.visit_methods).correct(visit_method) + end + + DidYouMean.correct_error(VisitMethodError, self) + end + + class << self + # This method is here to help folks write visitors. + # + # It's not always easy to ensure you're writing the correct method name in + # the visitor since it's perfectly valid to define methods that don't + # override these parent methods. + # + # If you use this method, you can ensure you're writing the correct method + # name. It will raise an error if the visit method you're defining isn't + # actually a method on the parent visitor. + def visit_method(method_name) + return if visit_methods.include?(method_name) + + raise VisitMethodError, method_name + end + + # This is the list of all of the valid visit methods. + def visit_methods + @visit_methods ||= + Visitor.instance_methods.grep(/^visit_(?!child_nodes)/) + end + end + + def visit(node) + node&.accept(self) + end + + def visit_all(nodes) + nodes.map { |node| visit(node) } + end + + def visit_child_nodes(node) + visit_all(node.child_nodes) + end + + # Visit an ARef node. + alias visit_aref visit_child_nodes + + # Visit an ARefField node. + alias visit_aref_field visit_child_nodes + + # Visit an Alias node. + alias visit_alias visit_child_nodes + + # Visit an ArgBlock node. + alias visit_arg_block visit_child_nodes + + # Visit an ArgParen node. + alias visit_arg_paren visit_child_nodes + + # Visit an ArgStar node. + alias visit_arg_star visit_child_nodes + + # Visit an Args node. + alias visit_args visit_child_nodes + + # Visit an ArgsForward node. + alias visit_args_forward visit_child_nodes + + # Visit an ArrayLiteral node. + alias visit_array visit_child_nodes + + # Visit an AryPtn node. + alias visit_aryptn visit_child_nodes + + # Visit an Assign node. + alias visit_assign visit_child_nodes + + # Visit an Assoc node. + alias visit_assoc visit_child_nodes + + # Visit an AssocSplat node. + alias visit_assoc_splat visit_child_nodes + + # Visit a Backref node. + alias visit_backref visit_child_nodes + + # Visit a Backtick node. + alias visit_backtick visit_child_nodes + + # Visit a BareAssocHash node. + alias visit_bare_assoc_hash visit_child_nodes + + # Visit a BEGINBlock node. + alias visit_BEGIN visit_child_nodes + + # Visit a Begin node. + alias visit_begin visit_child_nodes + + # Visit a Binary node. + alias visit_binary visit_child_nodes + + # Visit a BlockArg node. + alias visit_blockarg visit_child_nodes + + # Visit a BlockVar node. + alias visit_block_var visit_child_nodes + + # Visit a BodyStmt node. + alias visit_bodystmt visit_child_nodes + + # Visit a BraceBlock node. + alias visit_brace_block visit_child_nodes + + # Visit a Break node. + alias visit_break visit_child_nodes + + # Visit a Call node. + alias visit_call visit_child_nodes + + # Visit a Case node. + alias visit_case visit_child_nodes + + # Visit a CHAR node. + alias visit_CHAR visit_child_nodes + + # Visit a ClassDeclaration node. + alias visit_class visit_child_nodes + + # Visit a Comma node. + alias visit_comma visit_child_nodes + + # Visit a Command node. + alias visit_command visit_child_nodes + + # Visit a CommandCall node. + alias visit_command_call visit_child_nodes + + # Visit a Comment node. + alias visit_comment visit_child_nodes + + # Visit a Const node. + alias visit_const visit_child_nodes + + # Visit a ConstPathField node. + alias visit_const_path_field visit_child_nodes + + # Visit a ConstPathRef node. + alias visit_const_path_ref visit_child_nodes + + # Visit a ConstRef node. + alias visit_const_ref visit_child_nodes + + # Visit a CVar node. + alias visit_cvar visit_child_nodes + + # Visit a Def node. + alias visit_def visit_child_nodes + + # Visit a DefEndless node. + alias visit_def_endless visit_child_nodes + + # Visit a Defined node. + alias visit_defined visit_child_nodes + + # Visit a Defs node. + alias visit_defs visit_child_nodes + + # Visit a DoBlock node. + alias visit_do_block visit_child_nodes + + # Visit a Dot2 node. + alias visit_dot2 visit_child_nodes + + # Visit a Dot3 node. + alias visit_dot3 visit_child_nodes + + # Visit a DynaSymbol node. + alias visit_dyna_symbol visit_child_nodes + + # Visit an ENDBlock node. + alias visit_END visit_child_nodes + + # Visit an Else node. + alias visit_else visit_child_nodes + + # Visit an Elsif node. + alias visit_elsif visit_child_nodes + + # Visit an EmbDoc node. + alias visit_embdoc visit_child_nodes + + # Visit an EmbExprBeg node. + alias visit_embexpr_beg visit_child_nodes + + # Visit an EmbExprEnd node. + alias visit_embexpr_end visit_child_nodes + + # Visit an EmbVar node. + alias visit_embvar visit_child_nodes + + # Visit an Ensure node. + alias visit_ensure visit_child_nodes + + # Visit an ExcessedComma node. + alias visit_excessed_comma visit_child_nodes + + # Visit a FCall node. + alias visit_fcall visit_child_nodes + + # Visit a Field node. + alias visit_field visit_child_nodes + + # Visit a FloatLiteral node. + alias visit_float visit_child_nodes + + # Visit a FndPtn node. + alias visit_fndptn visit_child_nodes + + # Visit a For node. + alias visit_for visit_child_nodes + + # Visit a GVar node. + alias visit_gvar visit_child_nodes + + # Visit a HashLiteral node. + alias visit_hash visit_child_nodes + + # Visit a Heredoc node. + alias visit_heredoc visit_child_nodes + + # Visit a HeredocBeg node. + alias visit_heredoc_beg visit_child_nodes + + # Visit a HshPtn node. + alias visit_hshptn visit_child_nodes + + # Visit an Ident node. + alias visit_ident visit_child_nodes + + # Visit an If node. + alias visit_if visit_child_nodes + + # Visit an IfMod node. + alias visit_if_mod visit_child_nodes + + # Visit an IfOp node. + alias visit_if_op visit_child_nodes + + # Visit an Imaginary node. + alias visit_imaginary visit_child_nodes + + # Visit an In node. + alias visit_in visit_child_nodes + + # Visit an Int node. + alias visit_int visit_child_nodes + + # Visit an IVar node. + alias visit_ivar visit_child_nodes + + # Visit a Kw node. + alias visit_kw visit_child_nodes + + # Visit a KwRestParam node. + alias visit_kwrest_param visit_child_nodes + + # Visit a Label node. + alias visit_label visit_child_nodes + + # Visit a LabelEnd node. + alias visit_label_end visit_child_nodes + + # Visit a Lambda node. + alias visit_lambda visit_child_nodes + + # Visit a LBrace node. + alias visit_lbrace visit_child_nodes + + # Visit a LBracket node. + alias visit_lbracket visit_child_nodes + + # Visit a LParen node. + alias visit_lparen visit_child_nodes + + # Visit a MAssign node. + alias visit_massign visit_child_nodes + + # Visit a MethodAddBlock node. + alias visit_method_add_block visit_child_nodes + + # Visit a MLHS node. + alias visit_mlhs visit_child_nodes + + # Visit a MLHSParen node. + alias visit_mlhs_paren visit_child_nodes + + # Visit a ModuleDeclaration node. + alias visit_module visit_child_nodes + + # Visit a MRHS node. + alias visit_mrhs visit_child_nodes + + # Visit a Next node. + alias visit_next visit_child_nodes + + # Visit a Not node. + alias visit_not visit_child_nodes + + # Visit an Op node. + alias visit_op visit_child_nodes + + # Visit an OpAssign node. + alias visit_opassign visit_child_nodes + + # Visit a Params node. + alias visit_params visit_child_nodes + + # Visit a Paren node. + alias visit_paren visit_child_nodes + + # Visit a Period node. + alias visit_period visit_child_nodes + + # Visit a PinnedBegin node. + alias visit_pinned_begin visit_child_nodes + + # Visit a PinnedVarRef node. + alias visit_pinned_var_ref visit_child_nodes + + # Visit a Program node. + alias visit_program visit_child_nodes + + # Visit a QSymbols node. + alias visit_qsymbols visit_child_nodes + + # Visit a QSymbolsBeg node. + alias visit_qsymbols_beg visit_child_nodes + + # Visit a QWords node. + alias visit_qwords visit_child_nodes + + # Visit a QWordsBeg node. + alias visit_qwords_beg visit_child_nodes + + # Visit a RAssign node. + alias visit_rassign visit_child_nodes + + # Visit a RationalLiteral node. + alias visit_rational visit_child_nodes + + # Visit a RBrace node. + alias visit_rbrace visit_child_nodes + + # Visit a RBracket node. + alias visit_rbracket visit_child_nodes + + # Visit a Redo node. + alias visit_redo visit_child_nodes + + # Visit a RegexpBeg node. + alias visit_regexp_beg visit_child_nodes + + # Visit a RegexpContent node. + alias visit_regexp_content visit_child_nodes + + # Visit a RegexpEnd node. + alias visit_regexp_end visit_child_nodes + + # Visit a RegexpLiteral node. + alias visit_regexp_literal visit_child_nodes + + # Visit a Rescue node. + alias visit_rescue visit_child_nodes + + # Visit a RescueEx node. + alias visit_rescue_ex visit_child_nodes + + # Visit a RescueMod node. + alias visit_rescue_mod visit_child_nodes + + # Visit a RestParam node. + alias visit_rest_param visit_child_nodes + + # Visit a Retry node. + alias visit_retry visit_child_nodes + + # Visit a Return node. + alias visit_return visit_child_nodes + + # Visit a Return0 node. + alias visit_return0 visit_child_nodes + + # Visit a RParen node. + alias visit_rparen visit_child_nodes + + # Visit a SClass node. + alias visit_sclass visit_child_nodes + + # Visit a Statements node. + alias visit_statements visit_child_nodes + + # Visit a StringConcat node. + alias visit_string_concat visit_child_nodes + + # Visit a StringContent node. + alias visit_string_content visit_child_nodes + + # Visit a StringDVar node. + alias visit_string_dvar visit_child_nodes + + # Visit a StringEmbExpr node. + alias visit_string_embexpr visit_child_nodes + + # Visit a StringLiteral node. + alias visit_string_literal visit_child_nodes + + # Visit a Super node. + alias visit_super visit_child_nodes + + # Visit a SymBeg node. + alias visit_symbeg visit_child_nodes + + # Visit a SymbolContent node. + alias visit_symbol_content visit_child_nodes + + # Visit a SymbolLiteral node. + alias visit_symbol_literal visit_child_nodes + + # Visit a Symbols node. + alias visit_symbols visit_child_nodes + + # Visit a SymbolsBeg node. + alias visit_symbols_beg visit_child_nodes + + # Visit a TLambda node. + alias visit_tlambda visit_child_nodes + + # Visit a TLamBeg node. + alias visit_tlambeg visit_child_nodes + + # Visit a TopConstField node. + alias visit_top_const_field visit_child_nodes + + # Visit a TopConstRef node. + alias visit_top_const_ref visit_child_nodes + + # Visit a TStringBeg node. + alias visit_tstring_beg visit_child_nodes + + # Visit a TStringContent node. + alias visit_tstring_content visit_child_nodes + + # Visit a TStringEnd node. + alias visit_tstring_end visit_child_nodes + + # Visit an Unary node. + alias visit_unary visit_child_nodes + + # Visit an Undef node. + alias visit_undef visit_child_nodes + + # Visit an Unless node. + alias visit_unless visit_child_nodes + + # Visit an UnlessMod node. + alias visit_unless_mod visit_child_nodes + + # Visit an Until node. + alias visit_until visit_child_nodes + + # Visit an UntilMod node. + alias visit_until_mod visit_child_nodes + + # Visit a VarAlias node. + alias visit_var_alias visit_child_nodes + + # Visit a VarField node. + alias visit_var_field visit_child_nodes + + # Visit a VarRef node. + alias visit_var_ref visit_child_nodes + + # Visit a VCall node. + alias visit_vcall visit_child_nodes + + # Visit a VoidStmt node. + alias visit_void_stmt visit_child_nodes + + # Visit a When node. + alias visit_when visit_child_nodes + + # Visit a While node. + alias visit_while visit_child_nodes + + # Visit a WhileMod node. + alias visit_while_mod visit_child_nodes + + # Visit a Word node. + alias visit_word visit_child_nodes + + # Visit a Words node. + alias visit_words visit_child_nodes + + # Visit a WordsBeg node. + alias visit_words_beg visit_child_nodes + + # Visit a XString node. + alias visit_xstring visit_child_nodes + + # Visit a XStringLiteral node. + alias visit_xstring_literal visit_child_nodes + + # Visit a Yield node. + alias visit_yield visit_child_nodes + + # Visit a Yield0 node. + alias visit_yield0 visit_child_nodes + + # Visit a ZSuper node. + alias visit_zsuper visit_child_nodes + + # Visit an EndContent node. + alias visit___end__ visit_child_nodes + end +end diff --git a/lib/syntax_tree/visitor/json_visitor.rb b/lib/syntax_tree/visitor/json_visitor.rb new file mode 100644 index 00000000..9f0c8f94 --- /dev/null +++ b/lib/syntax_tree/visitor/json_visitor.rb @@ -0,0 +1,1335 @@ + # frozen_string_literal: true + +module SyntaxTree + class Visitor + class JSONVisitor < Visitor + def visit_aref(node) + { + type: :aref, + collection: visit(node.collection), + index: visit(node.index), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_aref_field(node) + { + type: :aref_field, + collection: visit(node.collection), + index: visit(node.index), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_alias(node) + { + type: :alias, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_arg_block(node) + { + type: :arg_block, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_arg_paren(node) + { + type: :arg_paren, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_arg_star(node) + { + type: :arg_star, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_args(node) + { + type: :args, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_args_forward(node) + visit_token(:args_forward, node) + end + + def visit_array(node) + { + type: :array, + cnts: visit(node.contents), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_aryptn(node) + { + type: :aryptn, + constant: visit(node.constant), + reqs: visit_all(node.requireds), + rest: visit(node.rest), + posts: visit_all(node.posts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_assign(node) + { + type: :assign, + target: visit(node.target), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_assoc(node) + { + type: :assoc, + key: visit(node.key), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_assoc_splat(node) + { + type: :assoc_splat, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_backref(node) + visit_token(:backref, node) + end + + def visit_backtick(node) + visit_token(:backtick, node) + end + + def visit_bare_assoc_hash(node) + { + type: :bare_assoc_hash, + assocs: visit_all(node.assocs), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_BEGIN(node) + { + type: :BEGIN, + lbrace: visit(node.lbrace), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_begin(node) + { + type: :begin, + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_binary(node) + { + type: :binary, + left: visit(node.left), + op: node.operator, + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_blockarg(node) + { + type: :blockarg, + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_block_var(node) + { + type: :block_var, + params: visit(node.params), + locals: visit_all(node.locals), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_bodystmt(node) + { + type: :bodystmt, + stmts: visit(node.statements), + rsc: visit(node.rescue_clause), + els: visit(node.else_clause), + ens: visit(node.ensure_clause), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_brace_block(node) + { + type: :brace_block, + lbrace: visit(node.lbrace), + block_var: visit(node.block_var), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_break(node) + { + type: :break, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_call(node) + { + type: :call, + receiver: visit(node.receiver), + op: visit_call_operator(node.operator), + message: node.message == :call ? :call : visit(node.message), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_case(node) + { + type: :case, + value: visit(node.value), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_CHAR(node) + visit_token(:CHAR, node) + end + + def visit_class(node) + { + type: :class, + constant: visit(node.constant), + superclass: visit(node.superclass), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_comma(node) + visit_token(:comma, node) + end + + def visit_command(node) + { + type: :command, + message: visit(node.message), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_command_call(node) + { + type: :command_call, + receiver: visit(node.receiver), + op: visit_call_operator(node.operator), + message: visit(node.message), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_comment(node) + { + type: :comment, + value: node.value, + inline: node.inline, + loc: visit_location(node.location) + } + end + + def visit_const(node) + visit_token(:const, node) + end + + def visit_const_path_field(node) + { + type: :const_path_field, + parent: visit(node.parent), + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_const_path_ref(node) + { + type: :const_path_ref, + parent: visit(node.parent), + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_const_ref(node) + { + type: :const_ref, + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_cvar(node) + visit_token(:cvar, node) + end + + def visit_def(node) + { + type: :def, + name: visit(node.name), + params: visit(node.params), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_def_endless(node) + { + type: :def_endless, + name: visit(node.name), + paren: visit(node.paren), + stmt: visit(node.statement), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_defined(node) + visit_token(:defined, node) + end + + def visit_defs(node) + { + type: :defs, + target: visit(node.target), + op: visit(node.operator), + name: visit(node.name), + params: visit(node.params), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_do_block(node) + { + type: :do_block, + keyword: visit(node.keyword), + block_var: visit(node.block_var), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_dot2(node) + { + type: :dot2, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_dot3(node) + { + type: :dot3, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_dyna_symbol(node) + { + type: :dyna_symbol, + parts: visit_all(node.parts), + quote: node.quote, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_END(node) + { + type: :END, + lbrace: visit(node.lbrace), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_else(node) + { + type: :else, + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_elsif(node) + { + type: :elsif, + pred: visit(node.predicate), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_embdoc(node) + { + type: :embdoc, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_embexpr_beg(node) + { + type: :embexpr_beg, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_embexpr_end(node) + { + type: :embexpr_end, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_embvar(node) + { + type: :embvar, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_ensure(node) + { + type: :ensure, + keyword: visit(node.keyword), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_excessed_comma(node) + visit_token(:excessed_comma, node) + end + + def visit_fcall(node) + { + type: :fcall, + value: visit(node.value), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_field(node) + { + type: :field, + parent: visit(node.parent), + op: visit_call_operator(node.operator), + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_float(node) + visit_token(:float, node) + end + + def visit_fndptn(node) + { + type: :fndptn, + constant: visit(node.constant), + left: visit(node.left), + values: visit_all(node.values), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_for(node) + { + type: :for, + index: visit(node.index), + collection: visit(node.collection), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_gvar(node) + visit_token(:gvar, node) + end + + def visit_hash(node) + { + type: :hash, + assocs: visit_all(node.assocs), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_heredoc(node) + { + type: :heredoc, + beging: visit(node.beginning), + ending: node.ending, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_heredoc_beg(node) + visit_token(:heredoc_beg, node) + end + + def visit_hshptn(node) + { + type: :hshptn, + constant: visit(node.constant), + keywords: node.keywords.map { |(name, value)| [visit(name), visit(value)] }, + kwrest: visit(node.keyword_rest), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_ident(node) + visit_token(:ident, node) + end + + def visit_if(node) + { + type: :if, + pred: visit(node.predicate), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_if_mod(node) + { + type: :if_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_if_op(node) + { + type: :ifop, + pred: visit(node.predicate), + tthy: visit(node.truthy), + flsy: visit(node.falsy), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_imaginary(node) + visit_token(:imaginary, node) + end + + def visit_in(node) + { + type: :in, + pattern: visit(node.pattern), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_int(node) + visit_token(:int, node) + end + + def visit_ivar(node) + visit_token(:ivar, node) + end + + def visit_kw(node) + visit_token(:kw, node) + end + + def visit_kwrest_param(node) + { + type: :kwrest_param, + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_label(node) + visit_token(:label, node) + end + + def visit_label_end(node) + visit_token(:label_end, node) + end + + def visit_lambda(node) + { + type: :lambda, + params: visit(node.params), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_lbrace(node) + visit_token(:lbrace, node) + end + + def visit_lbracket(node) + visit_token(:lbracket, node) + end + + def visit_lparen(node) + visit_token(:lparen, node) + end + + def visit_massign(node) + { + type: :massign, + target: visit(node.target), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_method_add_block(node) + { + type: :method_add_block, + call: visit(node.call), + block: visit(node.block), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_mlhs(node) + { + type: :mlhs, + parts: visit_all(node.parts), + comma: node.comma, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_mlhs_paren(node) + { + type: :mlhs_paren, + cnts: visit(node.contents), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_module(node) + { + type: :module, + constant: visit(node.constant), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_mrhs(node) + { + type: :mrhs, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_next(node) + { + type: :next, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_not(node) + { + type: :not, + value: visit(node.statement), + paren: node.parentheses, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_op(node) + visit_token(:op, node) + end + + def visit_opassign(node) + { + type: :opassign, + target: visit(node.target), + op: visit(node.operator), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_params(node) + { + type: :params, + reqs: visit_all(node.requireds), + opts: node.optionals.map { |(name, value)| [visit(name), visit(value)] }, + rest: visit(node.rest), + posts: visit_all(node.posts), + keywords: node.keywords.map { |(name, value)| [visit(name), visit(value || nil)] }, + kwrest: node.keyword_rest == :nil ? "nil" : visit(node.keyword_rest), + block: visit(node.block), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_paren(node) + { + type: :paren, + lparen: visit(node.lparen), + cnts: visit(node.contents), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_period(node) + visit_token(:period, node) + end + + def visit_pinned_begin(node) + { + type: :pinned_begin, + stmt: visit(node.statement), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_pinned_var_ref(node) + { + type: :pinned_var_ref, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_program(node) + { + type: :program, + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_qsymbols(node) + { + type: :qsymbols, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_qsymbols_beg(node) + visit_token(:qsymbols_beg, node) + end + + def visit_qwords(node) + { + type: :qwords, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_qwords_beg(node) + visit_token(:qwords_beg, node) + end + + def visit_rassign(node) + { + type: :rassign, + value: visit(node.value), + op: visit(node.operator), + pattern: visit(node.pattern), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rational(node) + visit_token(:rational, node) + end + + def visit_rbrace(node) + visit_token(:rbrace, node) + end + + def visit_rbracket(node) + visit_token(:rbracket, node) + end + + def visit_redo(node) + visit_token(:redo, node) + end + + def visit_regexp_beg(node) + visit_token(:regexp_beg, node) + end + + def visit_regexp_content(node) + { + type: :regexp_content, + beging: node.beginning, + parts: visit_all(node.parts), + loc: visit_location(node.location) + } + end + + def visit_regexp_end(node) + visit_token(:regexp_end, node) + end + + def visit_regexp_literal(node) + { + type: :regexp_literal, + beging: node.beginning, + ending: node.ending, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rescue(node) + { + type: :rescue, + extn: visit(node.exception), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rescue_ex(node) + { + type: :rescue_ex, + extns: visit(node.exceptions), + var: visit(node.variable), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rescue_mod(node) + { + type: :rescue_mod, + stmt: visit(node.statement), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rest_param(node) + { + type: :rest_param, + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_retry(node) + visit_token(:retry, node) + end + + def visit_return(node) + { + type: :return, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_return0(node) + visit_token(:return0, node) + end + + def visit_rparen(node) + visit_token(:rparen, node) + end + + def visit_sclass(node) + { + type: :sclass, + target: visit(node.target), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_statements(node) + { + type: :statements, + body: visit_all(node.body), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_concat(node) + { + type: :string_concat, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_content(node) + { + type: :string_content, + parts: visit_all(node.parts), + loc: visit_location(node.location) + } + end + + def visit_string_dvar(node) + { + type: :string_dvar, + var: visit(node.variable), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_embexpr(node) + { + type: :string_embexpr, + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_literal(node) + { + type: :string_literal, + parts: visit_all(node.parts), + quote: node.quote, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_super(node) + { + type: :super, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_symbeg(node) + visit_token(:symbeg, node) + end + + def visit_symbol_content(node) + { + type: :symbol_content, + value: visit(node.value), + loc: visit_location(node.location) + } + end + + def visit_symbol_literal(node) + { + type: :symbol_literal, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_symbols(node) + { + type: :symbols, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_symbols_beg(node) + visit_token(:symbols_beg, node) + end + + def visit_tlambda(node) + visit_token(:tlambda, node) + end + + def visit_tlambeg(node) + visit_token(:tlambeg, node) + end + + def visit_top_const_field(node) + { + type: :top_const_field, + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_top_const_ref(node) + { + type: :top_const_ref, + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_tstring_beg(node) + visit_token(:tstring_beg, node) + end + + def visit_tstring_content(node) + visit_token(:tstring_content, node) + end + + def visit_tstring_end(node) + visit_token(:tstring_end, node) + end + + def visit_unary(node) + { + type: :unary, + op: node.operator, + value: visit(node.statement), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_undef(node) + { + type: :undef, + syms: visit_all(node.symbols), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_unless(node) + { + type: :unless, + pred: visit(node.predicate), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_unless_mod(node) + { + type: :unless_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_until(node) + { + type: :until, + pred: visit(node.predicate), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_until_mod(node) + { + type: :until_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_var_alias(node) + { + type: :var_alias, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_var_field(node) + { + type: :var_field, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_var_ref(node) + { + type: :var_ref, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_vcall(node) + { + type: :vcall, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_void_stmt(node) + { + type: :void_stmt, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_when(node) + { + type: :when, + args: visit(node.arguments), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_while(node) + { + type: :while, + pred: visit(node.predicate), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_while_mod(node) + { + type: :while_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_word(node) + { + type: :word, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_words(node) + { + type: :words, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_words_beg(node) + visit_token(:words_beg, node) + end + + def visit_xstring(node) + { + type: :xstring, + parts: visit_all(node.parts), + loc: visit_location(node.location) + } + end + + def visit_xstring_literal(node) + { + type: :xstring_literal, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_yield(node) + { + type: :yield, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_yield0(node) + visit_token(:yield0, node) + end + + def visit_zsuper(node) + visit_token(:zsuper, node) + end + + def visit___end__(node) + visit_token(:__end__, node) + end + + private + + def visit_call_operator(operator) + operator == :"::" ? :"::" : visit(operator) + end + + def visit_location(location) + [ + location.start_line, + location.start_char, + location.end_line, + location.end_char + ] + end + + def visit_token(type, node) + { + type: type, + value: node.value, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + end + end +end diff --git a/lib/syntax_tree/visitor/pretty_print_visitor.rb b/lib/syntax_tree/visitor/pretty_print_visitor.rb new file mode 100644 index 00000000..40420a15 --- /dev/null +++ b/lib/syntax_tree/visitor/pretty_print_visitor.rb @@ -0,0 +1,1213 @@ + # frozen_string_literal: true + +module SyntaxTree + class Visitor + class PrettyPrintVisitor < Visitor + attr_reader :q + + def initialize(q) + @q = q + end + + def visit_aref(node) + node("aref") do + field("collection", node.collection) + field("index", node.index) + comments(node) + end + end + + def visit_aref_field(node) + node("aref_field") do + field("collection", node.collection) + field("index", node.index) + comments(node) + end + end + + def visit_alias(node) + node("alias") do + field("left", node.left) + field("right", node.right) + comments(node) + end + end + + def visit_arg_block(node) + node("arg_block") do + field("value", node.value) if node.value + comments(node) + end + end + + def visit_arg_paren(node) + node("arg_paren") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_arg_star(node) + node("arg_star") do + field("value", node.value) + comments(node) + end + end + + def visit_args(node) + node("args") do + list("parts", node.parts) + comments(node) + end + end + + def visit_args_forward(node) + visit_token("args_forward", node) + end + + def visit_array(node) + node("array") do + field("contents", node.contents) + comments(node) + end + end + + def visit_aryptn(node) + node("aryptn") do + field("constant", node.constant) if node.constant + list("requireds", node.requireds) if node.requireds.any? + field("rest", node.rest) if node.rest + list("posts", node.posts) if node.posts.any? + comments(node) + end + end + + def visit_assign(node) + node("assign") do + field("target", node.target) + field("value", node.value) + comments(node) + end + end + + def visit_assoc(node) + node("assoc") do + field("key", node.key) + field("value", node.value) if node.value + comments(node) + end + end + + def visit_assoc_splat(node) + node("assoc_splat") do + field("value", node.value) + comments(node) + end + end + + def visit_backref(node) + visit_token("backref", node) + end + + def visit_backtick(node) + visit_token("backtick", node) + end + + def visit_bare_assoc_hash(node) + node("bare_assoc_hash") do + list("assocs", node.assocs) + comments(node) + end + end + + def visit_BEGIN(node) + node("BEGIN") do + field("statements", node.statements) + comments(node) + end + end + + def visit_begin(node) + node("begin") do + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_binary(node) + node("binary") do + field("left", node.left) + text("operator", node.operator) + field("right", node.right) + comments(node) + end + end + + def visit_blockarg(node) + node("blockarg") do + field("name", node.name) if node.name + comments(node) + end + end + + def visit_block_var(node) + node("block_var") do + field("params", node.params) + list("locals", node.locals) if node.locals.any? + comments(node) + end + end + + def visit_bodystmt(node) + node("bodystmt") do + field("statements", node.statements) + field("rescue_clause", node.rescue_clause) if node.rescue_clause + field("else_clause", node.else_clause) if node.else_clause + field("ensure_clause", node.ensure_clause) if node.ensure_clause + comments(node) + end + end + + def visit_brace_block(node) + node("brace_block") do + field("block_var", node.block_var) if node.block_var + field("statements", node.statements) + comments(node) + end + end + + def visit_break(node) + node("break") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_call(node) + node("call") do + field("receiver", node.receiver) + field("operator", node.operator) + field("message", node.message) + field("arguments", node.arguments) if node.arguments + comments(node) + end + end + + def visit_case(node) + node("case") do + field("keyword", node.keyword) + field("value", node.value) if node.value + field("consequent", node.consequent) + comments(node) + end + end + + def visit_CHAR(node) + visit_token("CHAR", node) + end + + def visit_class(node) + node("class") do + field("constant", node.constant) + field("superclass", node.superclass) if node.superclass + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_comma(node) + node("comma") do + field("value", node) + end + end + + def visit_command(node) + node("command") do + field("message", node.message) + field("arguments", node.arguments) + comments(node) + end + end + + def visit_command_call(node) + node("command_call") do + field("receiver", node.receiver) + field("operator", node.operator) + field("message", node.message) + field("arguments", node.arguments) if node.arguments + comments(node) + end + end + + def visit_comment(node) + node("comment") do + field("value", node.value) + end + end + + def visit_const(node) + visit_token("const", node) + end + + def visit_const_path_field(node) + node("const_path_field") do + field("parent", node.parent) + field("constant", node.constant) + comments(node) + end + end + + def visit_const_path_ref(node) + node("const_path_ref") do + field("parent", node.parent) + field("constant", node.constant) + comments(node) + end + end + + def visit_const_ref(node) + node("const_ref") do + field("constant", node.constant) + comments(node) + end + end + + def visit_cvar(node) + visit_token("cvar", node) + end + + def visit_def(node) + node("def") do + field("name", node.name) + field("params", node.params) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_def_endless(node) + node("def_endless") do + if node.target + field("target", node.target) + field("operator", node.operator) + end + + field("name", node.name) + field("paren", node.paren) if node.paren + field("statement", node.statement) + comments(node) + end + end + + def visit_defined(node) + node("defined") do + field("value", node.value) + comments(node) + end + end + + def visit_defs(node) + node("defs") do + field("target", node.target) + field("operator", node.operator) + field("name", node.name) + field("params", node.params) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_do_block(node) + node("do_block") do + field("block_var", node.block_var) if node.block_var + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_dot2(node) + node("dot2") do + field("left", node.left) if node.left + field("right", node.right) if node.right + comments(node) + end + end + + def visit_dot3(node) + node("dot3") do + field("left", node.left) if node.left + field("right", node.right) if node.right + comments(node) + end + end + + def visit_dyna_symbol(node) + node("dyna_symbol") do + list("parts", node.parts) + comments(node) + end + end + + def visit_END(node) + node("END") do + field("statements", node.statements) + comments(node) + end + end + + def visit_else(node) + node("else") do + field("statements", node.statements) + comments(node) + end + end + + def visit_elsif(node) + node("elsif") do + field("predicate", node.predicate) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_embdoc(node) + node("embdoc") do + field("value", node.value) + end + end + + def visit_embexpr_beg(node) + node("embexpr_beg") do + field("value", node.value) + end + end + + def visit_embexpr_end(node) + node("embexpr_end") do + field("value", node.value) + end + end + + def visit_embvar(node) + node("embvar") do + field("value", node.value) + end + end + + def visit_ensure(node) + node("ensure") do + field("statements", node.statements) + comments(node) + end + end + + def visit_excessed_comma(node) + visit_token("excessed_comma", node) + end + + def visit_fcall(node) + node("fcall") do + field("value", node.value) + field("arguments", node.arguments) if node.arguments + comments(node) + end + end + + def visit_field(node) + node("field") do + field("parent", node.parent) + field("operator", node.operator) + field("name", node.name) + comments(node) + end + end + + def visit_float(node) + visit_token("float", node) + end + + def visit_fndptn(node) + node("fndptn") do + field("constant", node.constant) if node.constant + field("left", node.left) + list("values", node.values) + field("right", node.right) + comments(node) + end + end + + def visit_for(node) + node("for") do + field("index", node.index) + field("collection", node.collection) + field("statements", node.statements) + comments(node) + end + end + + def visit_gvar(node) + visit_token("gvar", node) + end + + def visit_hash(node) + node("hash") do + list("assocs", node.assocs) if node.assocs.any? + comments(node) + end + end + + def visit_heredoc(node) + node("heredoc") do + list("parts", node.parts) + comments(node) + end + end + + def visit_heredoc_beg(node) + visit_token("heredoc_beg", node) + end + + def visit_hshptn(node) + node("hshptn") do + field("constant", node.constant) if node.constant + + if node.keywords.any? + q.breakable + q.group(2, "(", ")") do + q.seplist(node.keywords) do |(key, value)| + q.group(2, "(", ")") do + key.pretty_print(q) + + if value + q.breakable + value.pretty_print(q) + end + end + end + end + end + + field("keyword_rest", node.keyword_rest) if node.keyword_rest + comments(node) + end + end + + def visit_ident(node) + visit_token("ident", node) + end + + def visit_if(node) + node("if") do + field("predicate", node.predicate) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_if_mod(node) + node("if_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_if_op(node) + node("ifop") do + field("predicate", node.predicate) + field("truthy", node.truthy) + field("falsy", node.falsy) + comments(node) + end + end + + def visit_imaginary(node) + visit_token("imaginary", node) + end + + def visit_in(node) + node("in") do + field("pattern", node.pattern) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_int(node) + visit_token("int", node) + end + + def visit_ivar(node) + visit_token("ivar", node) + end + + def visit_kw(node) + visit_token("kw", node) + end + + def visit_kwrest_param(node) + node("kwrest_param") do + field("name", node.name) + comments(node) + end + end + + def visit_label(node) + node("label") do + q.breakable + q.text(":") + q.text(node.value[0...-1]) + comments(node) + end + end + + def visit_label_end(node) + node("label_end") do + field("value", node.value) + end + end + + def visit_lambda(node) + node("lambda") do + field("params", node.params) + field("statements", node.statements) + comments(node) + end + end + + def visit_lbrace(node) + visit_token("lbrace", node) + end + + def visit_lbracket(node) + visit_token("lbracket", node) + end + + def visit_lparen(node) + visit_token("lparen", node) + end + + def visit_massign(node) + node("massign") do + field("target", node.target) + field("value", node.value) + comments(node) + end + end + + def visit_method_add_block(node) + node("method_add_block") do + field("call", node.call) + field("block", node.block) + comments(node) + end + end + + def visit_mlhs(node) + node("mlhs") do + list("parts", node.parts) + comments(node) + end + end + + def visit_mlhs_paren(node) + node("mlhs_paren") do + field("contents", node.contents) + comments(node) + end + end + + def visit_module(node) + node("module") do + field("constant", node.constant) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_mrhs(node) + node("mrhs") do + list("parts", node.parts) + comments(node) + end + end + + def visit_next(node) + node("next") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_not(node) + node("not") do + field("statement", node.statement) + comments(node) + end + end + + def visit_op(node) + visit_token("op", node) + end + + def visit_opassign(node) + node("opassign") do + field("target", node.target) + field("operator", node.operator) + field("value", node.value) + comments(node) + end + end + + def visit_params(node) + node("params") do + list("requireds", node.requireds) if node.requireds.any? + + if node.optionals.any? + q.breakable + q.group(2, "(", ")") do + q.seplist(node.optionals) do |(name, default)| + name.pretty_print(q) + q.text("=") + q.group(2) do + q.breakable("") + default.pretty_print(q) + end + end + end + end + + field("rest", node.rest) if node.rest + list("posts", node.posts) if node.posts.any? + + if node.keywords.any? + q.breakable + q.group(2, "(", ")") do + q.seplist(node.keywords) do |(name, default)| + name.pretty_print(q) + + if default + q.text("=") + q.group(2) do + q.breakable("") + default.pretty_print(q) + end + end + end + end + end + + field("keyword_rest", node.keyword_rest) if node.keyword_rest + field("block", node.block) if node.block + comments(node) + end + end + + def visit_paren(node) + node("paren") do + field("contents", node.contents) + comments(node) + end + end + + def visit_period(node) + visit_token("period", node) + end + + def visit_pinned_begin(node) + node("pinned_begin") do + field("statement", node.statement) + comments(node) + end + end + + def visit_pinned_var_ref(node) + node("pinned_var_ref") do + field("value", node.value) + comments(node) + end + end + + def visit_program(node) + node("program") do + field("statements", node.statements) + comments(node) + end + end + + def visit_qsymbols(node) + node("qsymbols") do + list("elements", node.elements) + comments(node) + end + end + + def visit_qsymbols_beg(node) + node("qsymbols_beg") do + field("value", node.value) + end + end + + def visit_qwords(node) + node("qwords") do + list("elements", node.elements) + comments(node) + end + end + + def visit_qwords_beg(node) + node("qwords_beg") do + field("value", node.value) + end + end + + def visit_rassign(node) + node("rassign") do + field("value", node.value) + field("operator", node.operator) + field("pattern", node.pattern) + comments(node) + end + end + + def visit_rational(node) + visit_token("rational", node) + end + + def visit_rbrace(node) + node("rbrace") do + field("value", node.value) + end + end + + def visit_rbracket(node) + node("rbracket") do + field("value", node.value) + end + end + + def visit_redo(node) + visit_token("redo", node) + end + + def visit_regexp_beg(node) + node("regexp_beg") do + field("value", node.value) + end + end + + def visit_regexp_content(node) + node("regexp_content") do + list("parts", node.parts) + end + end + + def visit_regexp_end(node) + node("regexp_end") do + field("value", node.value) + end + end + + def visit_regexp_literal(node) + node("regexp_literal") do + list("parts", node.parts) + comments(node) + end + end + + def visit_rescue(node) + node("rescue") do + field("exception", node.exception) if node.exception + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_rescue_ex(node) + node("rescue_ex") do + field("exceptions", node.exceptions) + field("variable", node.variable) + comments(node) + end + end + + def visit_rescue_mod(node) + node("rescue_mod") do + field("statement", node.statement) + field("value", node.value) + comments(node) + end + end + + def visit_rest_param(node) + node("rest_param") do + field("name", node.name) + comments(node) + end + end + + def visit_retry(node) + visit_token("retry", node) + end + + def visit_return(node) + node("return") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_return0(node) + visit_token("return0", node) + end + + def visit_rparen(node) + node("rparen") do + field("value", node.value) + end + end + + def visit_sclass(node) + node("sclass") do + field("target", node.target) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_statements(node) + node("statements") do + list("body", node.body) + comments(node) + end + end + + def visit_string_concat(node) + node("string_concat") do + field("left", node.left) + field("right", node.right) + comments(node) + end + end + + def visit_string_content(node) + node("string_content") do + list("parts", node.parts) + end + end + + def visit_string_dvar(node) + node("string_dvar") do + field("variable", node.variable) + comments(node) + end + end + + def visit_string_embexpr(node) + node("string_embexpr") do + field("statements", node.statements) + comments(node) + end + end + + def visit_string_literal(node) + node("string_literal") do + list("parts", node.parts) + comments(node) + end + end + + def visit_super(node) + node("super") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_symbeg(node) + node("symbeg") do + field("value", node.value) + end + end + + def visit_symbol_content(node) + node("symbol_content") do + field("value", node.value) + end + end + + def visit_symbol_literal(node) + node("symbol_literal") do + field("value", node.value) + comments(node) + end + end + + def visit_symbols(node) + node("symbols") do + list("elements", node.elements) + comments(node) + end + end + + def visit_symbols_beg(node) + node("symbols_beg") do + field("value", node.value) + end + end + + def visit_tlambda(node) + node("tlambda") do + field("value", node.value) + end + end + + def visit_tlambeg(node) + node("tlambeg") do + field("value", node.value) + end + end + + def visit_top_const_field(node) + node("top_const_field") do + field("constant", node.constant) + comments(node) + end + end + + def visit_top_const_ref(node) + node("top_const_ref") do + field("constant", node.constant) + comments(node) + end + end + + def visit_tstring_beg(node) + node("tstring_beg") do + field("value", node.value) + end + end + + def visit_tstring_content(node) + visit_token("tstring_content", node) + end + + def visit_tstring_end(node) + node("tstring_end") do + field("value", node.value) + end + end + + def visit_unary(node) + node("unary") do + field("operator", node.operator) + field("statement", node.statement) + comments(node) + end + end + + def visit_undef(node) + node("undef") do + list("symbols", node.symbols) + comments(node) + end + end + + def visit_unless(node) + node("unless") do + field("predicate", node.predicate) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_unless_mod(node) + node("unless_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_until(node) + node("until") do + field("predicate", node.predicate) + field("statements", node.statements) + comments(node) + end + end + + def visit_until_mod(node) + node("until_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_var_alias(node) + node("var_alias") do + field("left", node.left) + field("right", node.right) + comments(node) + end + end + + def visit_var_field(node) + node("var_field") do + field("value", node.value) + comments(node) + end + end + + def visit_var_ref(node) + node("var_ref") do + field("value", node.value) + comments(node) + end + end + + def visit_vcall(node) + node("vcall") do + field("value", node.value) + comments(node) + end + end + + def visit_void_stmt(node) + node("void_stmt") do + comments(node) + end + end + + def visit_when(node) + node("when") do + field("arguments", node.arguments) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_while(node) + node("while") do + field("predicate", node.predicate) + field("statements", node.statements) + comments(node) + end + end + + def visit_while_mod(node) + node("while_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_word(node) + node("word") do + list("parts", node.parts) + comments(node) + end + end + + def visit_words(node) + node("words") do + list("elements", node.elements) + comments(node) + end + end + + def visit_words_beg(node) + node("words_beg") do + field("value", node.value) + end + end + + def visit_xstring(node) + node("xstring") do + list("parts", node.parts) + end + end + + def visit_xstring_literal(node) + node("xstring_literal") do + list("parts", node.parts) + comments(node) + end + end + + def visit_yield(node) + node("yield") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_yield0(node) + visit_token("yield0", node) + end + + def visit_zsuper(node) + visit_token("zsuper", node) + end + + def visit___end__(node) + visit_token("__end__", node) + end + + private + + def comments(node) + return if node.comments.empty? + + q.breakable + q.group(2, "(", ")") do + q.seplist(node.comments) { |comment| comment.pretty_print(q) } + end + end + + def field(_name, value) + q.breakable + + # I don't entirely know why this is necessary, but in Ruby 2.7 there is + # an issue with calling q.pp on strings that somehow involves inspect + # keys. I'm purposefully avoiding the inspect key stuff here because I + # know the tree does not contain any cycles. + value.is_a?(String) ? q.text(value.inspect) : value.pretty_print(q) + end + + def list(_name, values) + q.breakable + q.group(2, "(", ")") do + q.seplist(values) { |value| value.pretty_print(q) } + end + end + + def node(type) + q.group(2, "(", ")") do + q.text(type) + yield + end + end + + def text(_name, value) + q.breakable + q.text(value) + end + + def visit_token(type, node) + node(type) do + field("value", node.value) + comments(node) + end + end + end + end +end diff --git a/test/fixtures/begin.rb b/test/fixtures/begin.rb index f9e5b775..efd12dad 100644 --- a/test/fixtures/begin.rb +++ b/test/fixtures/begin.rb @@ -5,7 +5,3 @@ begin expression end -% -case value -in ^(expression) -end diff --git a/test/fixtures/bodystmt.rb b/test/fixtures/bodystmt.rb index 120255a8..4cbb8f5e 100644 --- a/test/fixtures/bodystmt.rb +++ b/test/fixtures/bodystmt.rb @@ -34,3 +34,23 @@ ensure foo end +% +begin +else # else +end +% +begin +ensure # ensure +end +% +begin +rescue # rescue +else # else +ensure # ensure +end +- +begin +rescue StandardError # rescue +else # else +ensure # ensure +end diff --git a/test/fixtures/command.rb b/test/fixtures/command.rb index d74ae8ab..7f061acd 100644 --- a/test/fixtures/command.rb +++ b/test/fixtures/command.rb @@ -5,3 +5,21 @@ - foo barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr, bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +% +meta1 def foo +end +% +meta2 meta1 def foo +end +% +meta3 meta2 meta1 def foo +end +% +meta1 def self.foo +end +% +meta2 meta1 def self.foo +end +% +meta3 meta2 meta1 def self.foo +end diff --git a/test/fixtures/command_def_endless.rb b/test/fixtures/command_def_endless.rb new file mode 100644 index 00000000..e6890b83 --- /dev/null +++ b/test/fixtures/command_def_endless.rb @@ -0,0 +1,6 @@ +% +meta1 def foo = 1 +% +meta2 meta1 def foo = 1 +% +meta3 meta2 meta1 def foo = 1 diff --git a/test/fixtures/else.rb b/test/fixtures/else.rb index d3675c27..e440514a 100644 --- a/test/fixtures/else.rb +++ b/test/fixtures/else.rb @@ -18,3 +18,7 @@ else bar end +% +if foo +else # bar +end diff --git a/test/fixtures/pinned_begin.rb b/test/fixtures/pinned_begin.rb new file mode 100644 index 00000000..d67ac9cf --- /dev/null +++ b/test/fixtures/pinned_begin.rb @@ -0,0 +1,4 @@ +% +case value +in ^(expression) +end diff --git a/test/fixtures/var_field.rb b/test/fixtures/var_field.rb index 2b78c098..8c1258af 100644 --- a/test/fixtures/var_field.rb +++ b/test/fixtures/var_field.rb @@ -8,13 +8,3 @@ foo = bar % @foo = bar -% -foo in bar -% -foo in ^bar -% -foo in ^@bar -% -foo in ^@@bar -% -foo in ^$gvar diff --git a/test/fixtures/var_field_rassign.rb b/test/fixtures/var_field_rassign.rb new file mode 100644 index 00000000..3e019c5c --- /dev/null +++ b/test/fixtures/var_field_rassign.rb @@ -0,0 +1,10 @@ +% +foo in bar +% +foo in ^bar +% +foo in ^@bar +% +foo in ^@@bar +% +foo in ^$gvar diff --git a/test/formatting_test.rb b/test/formatting_test.rb index f7c3b3f7..25059658 100644 --- a/test/formatting_test.rb +++ b/test/formatting_test.rb @@ -4,27 +4,9 @@ module SyntaxTree class FormattingTest < Minitest::Test - delimiter = /%(?: # (.+?))?\n/ - - Dir[File.join(__dir__, "fixtures", "*.rb")].each do |filepath| - basename = File.basename(filepath, ".rb") - sources = File.readlines(filepath).slice_before(delimiter) - - sources.each_with_index do |source, index| - comment = source.shift.match(delimiter)[1] - original, expected = source.join.split("-\n") - - # If there's a comment starting with >= that starts after the % that - # delineates the test, then we're going to check if the version - # satisfies that constraint. - if comment&.start_with?(">=") - version = Gem::Version.new(comment.split[1]) - next if Gem::Version.new(RUBY_VERSION) < version - end - - define_method(:"test_formatting_#{basename}_#{index}") do - assert_equal(expected || original, SyntaxTree.format(original)) - end + Fixtures.each_fixture do |fixture| + define_method(:"test_formatted_#{fixture.name}") do + assert_equal(fixture.formatted, SyntaxTree.format(fixture.source)) end end end diff --git a/test/json_visitor_test.rb b/test/json_visitor_test.rb new file mode 100644 index 00000000..917aca71 --- /dev/null +++ b/test/json_visitor_test.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module SyntaxTree + class JSONVisitorTest < Minitest::Test + Fixtures.each_fixture do |fixture| + define_method(:"test_json_#{fixture.name}") do + refute_includes(SyntaxTree.format(fixture.source).to_json, "#<") + end + end + end +end diff --git a/test/node_test.rb b/test/node_test.rb index 1382ed79..e412d648 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -289,12 +289,14 @@ def test_case assert_node(Case, "case", source) end - def test_rassign_in - assert_node(RAssign, "rassign", "value in pattern") - end + guard_version("3.0.0") do + def test_rassign_in + assert_node(RAssign, "rassign", "value in pattern") + end - def test_rassign_rocket - assert_node(RAssign, "rassign", "value => pattern") + def test_rassign_rocket + assert_node(RAssign, "rassign", "value => pattern") + end end def test_class @@ -352,8 +354,10 @@ def method assert_node(Def, "def", source) end - def test_def_endless - assert_node(DefEndless, "def_endless", "def method = result") + guard_version("3.0.0") do + def test_def_endless + assert_node(DefEndless, "def_endless", "def method = result") + end end guard_version("3.1.0") do @@ -478,16 +482,18 @@ def test_float_literal assert_node(FloatLiteral, "float", "1.0") end - def test_fndptn - source = <<~SOURCE - case value - in Container[*, 7, *] - end - SOURCE + guard_version("3.0.0") do + def test_fndptn + source = <<~SOURCE + case value + in Container[*, 7, *] + end + SOURCE - at = location(lines: 2..2, chars: 14..32) - assert_node(FndPtn, "fndptn", source, at: at) do |node| - node.consequent.pattern + at = location(lines: 2..2, chars: 14..32) + assert_node(FndPtn, "fndptn", source, at: at) do |node| + node.consequent.pattern + end end end @@ -993,14 +999,36 @@ def test_zsuper assert_node(ZSuper, "zsuper", "super") end + def test_column_positions + source = <<~SOURCE + puts 'Hello' + puts 'Goodbye' + SOURCE + + at = location(lines: 2..2, chars: 13..27, columns: 0..14) + assert_node(Command, "command", source, at: at) + end + + def test_multibyte_column_positions + source = <<~SOURCE + puts "Congrats" + puts "🎉 🎉" + SOURCE + + at = location(lines: 2..2, chars: 16..26, columns: 0..10) + assert_node(Command, "command", source, at: at) + end + private - def location(lines: 1..1, chars: 0..0) + def location(lines: 1..1, chars: 0..0, columns: 0..0) Location.new( start_line: lines.begin, start_char: chars.begin, + start_column: columns.begin, end_line: lines.end, - end_char: chars.end + end_char: chars.end, + end_column: columns.end ) end @@ -1008,7 +1036,8 @@ def assert_node(kind, type, source, at: nil) at ||= location( lines: 1..[1, source.count("\n")].max, - chars: 0..source.chomp.size + chars: 0..source.chomp.size, + columns: 0..source.chomp.size ) # Parse the example, get the outputted parse tree, and assert that it was diff --git a/test/parser_test.rb b/test/parser_test.rb new file mode 100644 index 00000000..e5861398 --- /dev/null +++ b/test/parser_test.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module SyntaxTree + class ParserTest < Minitest::Test + def test_parses_ripper_methods + # First, get a list of all of the dispatched events from ripper. + events = Ripper::EVENTS + + # Next, subtract all of the events that we have explicitly defined. + events -= Parser.private_instance_methods(false).grep(/^on_(\w+)/) { $1.to_sym } + + # Next, subtract the list of events that we purposefully skipped. + events -= %i[ + arg_ambiguous + assoclist_from_args + ignored_nl + ignored_sp + magic_comment + nl + nokw_param + operator_ambiguous + semicolon + sp + words_sep + ] + + # Finally, assert that we have no remaining events. + assert_empty(events) + end + end +end diff --git a/test/pretty_print_visitor_test.rb b/test/pretty_print_visitor_test.rb new file mode 100644 index 00000000..8ca7cdf8 --- /dev/null +++ b/test/pretty_print_visitor_test.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module SyntaxTree + class PrettyPrintVisitorTest < Minitest::Test + Fixtures.each_fixture do |fixture| + define_method(:"test_pretty_print_#{fixture.name}") do + formatter = PP.new([]) + + program = SyntaxTree.parse(fixture.source) + program.pretty_print(formatter) + + formatter.flush + refute_includes(formatter.output.join, "#<") + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 8512253c..09c4dd0a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -9,3 +9,61 @@ require "json" require "pp" require "minitest/autorun" + +# There are a bunch of fixtures defined in test/fixtures. They exercise every +# possible combination of syntax that leads to variations in the types of nodes. +# They are used for testing various parts of Syntax Tree, including formatting, +# serialization, and parsing. This module provides a single each_fixture method +# that can be used to drive tests on each fixture. +module Fixtures + FIXTURES_3_0_0 = %w[ + command_def_endless + def_endless + fndptn + rassign + rassign_rocket + ] + + FIXTURES_3_1_0 = %w[ + pinned_begin + var_field_rassign + ] + + Fixture = Struct.new(:name, :source, :formatted, keyword_init: true) + + def self.each_fixture + ruby_version = Gem::Version.new(RUBY_VERSION) + + # First, get a list of the basenames of all of the fixture files. + fixtures = + Dir[File.expand_path("fixtures/*.rb", __dir__)].map do |filepath| + File.basename(filepath, ".rb") + end + + # Next, subtract out any fixtures that aren't supported by the current Ruby + # version. + fixtures -= FIXTURES_3_1_0 if ruby_version < Gem::Version.new("3.1.0") + fixtures -= FIXTURES_3_0_0 if ruby_version < Gem::Version.new("3.0.0") + + delimiter = /%(?: # (.+?))?\n/ + fixtures.each do |fixture| + filepath = File.expand_path("fixtures/#{fixture}.rb", __dir__) + + # For each fixture in the fixture file yield a Fixture object. + File.readlines(filepath).slice_before(delimiter).each_with_index do |source, index| + comment = source.shift.match(delimiter)[1] + source, formatted = source.join.split("-\n") + + # If there's a comment starting with >= that starts after the % that + # delineates the test, then we're going to check if the version + # satisfies that constraint. + if comment&.start_with?(">=") + next if ruby_version < Gem::Version.new(comment.split[1]) + end + + name = :"#{fixture}_#{index}" + yield Fixture.new(name: name, source: source, formatted: formatted || source) + end + end + end +end diff --git a/test/visitor_test.rb b/test/visitor_test.rb new file mode 100644 index 00000000..dd9f1e11 --- /dev/null +++ b/test/visitor_test.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class VisitorTest < Minitest::Test + unless ENV["FAST"] + def test_visit_all_nodes + visitor = SyntaxTree::Visitor.new + + filepath = File.expand_path("../lib/syntax_tree/node.rb", __dir__) + program = SyntaxTree.parse(SyntaxTree.read(filepath)) + + program.statements.body.last.bodystmt.statements.body.each do |node| + case node + in SyntaxTree::ClassDeclaration[superclass: { value: { value: "Node" } }] + # this is a class we want to look at + else + next + end + + accept = + node.bodystmt.statements.body.detect do |defm| + case defm + in SyntaxTree::Def[name: { value: "accept" }] + true + else + false + end + end + + case accept + in { bodystmt: { statements: { body: [SyntaxTree::Call[message: { value: visit_method }]] } } } + assert_respond_to(visitor, visit_method) + end + end + end + end + + def test_visit_tree + parsed_tree = SyntaxTree.parse(<<~RUBY) + class Foo + def foo; end + + class Bar + def bar; end + end + end + + def baz; end + RUBY + + visitor = DummyVisitor.new + visitor.visit(parsed_tree) + assert_equal(["Foo", "foo", "Bar", "bar", "baz"], visitor.visited_nodes) + end + + class DummyVisitor < SyntaxTree::Visitor + attr_reader :visited_nodes + + def initialize + super + @visited_nodes = [] + end + + visit_method def visit_class(node) + @visited_nodes << node.constant.constant.value + super + end + + visit_method def visit_def(node) + @visited_nodes << node.name.value + end + end +end