diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 63d51a3c..7f5ac15c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,9 +24,28 @@ jobs: ruby-version: ${{ matrix.ruby }} - name: Test run: bundle exec rake test + + check: + name: Check + runs-on: ubuntu-latest + env: + CI: true + steps: + - uses: actions/checkout@master + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + ruby-version: '3.1' + - name: Check + run: | + bundle exec rake check + bundle exec rubocop + automerge: name: AutoMerge - needs: ci + needs: + - ci + - check runs-on: ubuntu-latest if: github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]' steps: diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..32db04ed --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,74 @@ +inherit_from: config/rubocop.yml + +AllCops: + DisplayCopNames: true + DisplayStyleGuide: true + NewCops: enable + SuggestExtensions: false + TargetRubyVersion: 2.7 + Exclude: + - '{bin,coverage,pkg,test/fixtures,vendor,tmp}/**/*' + - test.rb + +Layout/LineLength: + Max: 80 + +Lint/DuplicateBranch: + Enabled: false + +Lint/InterpolationCheck: + Enabled: false + +Lint/MissingSuper: + Enabled: false + +Lint/UnusedMethodArgument: + AllowUnusedKeywordArguments: true + +Metrics: + Enabled: false + +Naming/MethodName: + Enabled: false + +Naming/MethodParameterName: + Enabled: false + +Naming/RescuedExceptionsVariableName: + PreferredName: error + +Style/ExplicitBlockArgument: + Enabled: false + +Style/FormatString: + EnforcedStyle: percent + +Style/GuardClause: + Enabled: false + +Style/IdenticalConditionalBranches: + Enabled: false + +Style/IfInsideElse: + Enabled: false + +Style/KeywordParametersOrder: + Enabled: false + +Style/MutableConstant: + Enabled: false + +Style/NegatedIfElseCondition: + Enabled: false + +Style/NumericPredicate: + Enabled: false + +Style/ParallelAssignment: + Enabled: false + +Style/PerlBackrefs: + Enabled: false + +Style/SpecialGlobalVars: + Enabled: false diff --git a/Gemfile b/Gemfile index be173b20..73418542 100644 --- a/Gemfile +++ b/Gemfile @@ -3,3 +3,5 @@ source "https://rubygems.org" gemspec + +gem "rubocop" diff --git a/Gemfile.lock b/Gemfile.lock index 50166f2e..577cf16f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,15 +6,35 @@ PATH GEM remote: https://rubygems.org/ specs: + ast (2.4.2) docile (1.4.0) minitest (5.15.0) + parallel (1.22.1) + parser (3.1.2.0) + ast (~> 2.4.1) + rainbow (3.1.1) rake (13.0.6) + regexp_parser (2.3.1) + rexml (3.2.5) + rubocop (1.28.2) + parallel (~> 1.10) + parser (>= 3.1.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.17.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.17.0) + parser (>= 3.1.1.0) + ruby-progressbar (1.11.0) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) + unicode-display_width (2.1.0) PLATFORMS arm64-darwin-21 @@ -27,6 +47,7 @@ DEPENDENCIES bundler minitest rake + rubocop simplecov syntax_tree! diff --git a/README.md b/README.md index ce22548c..d952d878 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ It is built with only standard library dependencies. It additionally ships with - [ast](#ast) - [check](#check) - [format](#format) + - [json](#json) + - [match](#match) - [write](#write) - [Library](#library) - [SyntaxTree.read(filepath)](#syntaxtreereadfilepath) @@ -34,6 +36,9 @@ It is built with only standard library dependencies. It additionally ships with - [textDocument/inlayHints](#textdocumentinlayhints) - [syntaxTree/visualizing](#syntaxtreevisualizing) - [Plugins](#plugins) +- [Integration](#integration) + - [RuboCop](#rubocop) + - [VSCode](#vscode) - [Contributing](#contributing) - [License](#license) @@ -395,6 +400,25 @@ Below are listed all of the "official" plugins hosted under the same GitHub orga * [SyntaxTree::JSON](https://github.com/ruby-syntax-tree/syntax_tree-json) for JSON. * [SyntaxTree::RBS](https://github.com/ruby-syntax-tree/syntax_tree-rbs) for the [RBS type language](https://github.com/ruby/rbs). +When invoking the CLI, you pass through the list of plugins with the `--plugins` options to the commands that accept them. They should be a comma-delimited list. When the CLI first starts, it will require the files corresponding to those names. + +## Integration + +Syntax Tree's goal is to seemlessly integrate into your workflow. To this end, it provides a couple of additional tools beyond the CLI and the Ruby library. + +### RuboCop + +RuboCop and Syntax Tree serve different purposes, but there is overlap with some of RuboCop's functionality. Syntax Tree provides a RuboCop configuration file to disable rules that are redundant with Syntax Tree. To use this configuration file, add the following snippet to the top of your project's `.rubocop.yml`: + +```yaml +inherit_gem: + syntax_tree: config/rubocop.yml +``` + +### VSCode + +To integrate Syntax Tree into VSCode, you should use the official VSCode extension [ruby-syntax-tree/vscode-syntax-tree](https://github.com/ruby-syntax-tree/vscode-syntax-tree). + ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/ruby-syntax-tree/syntax_tree. diff --git a/Rakefile b/Rakefile index 4caa98e2..4b3de39a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,12 +1,34 @@ # frozen_string_literal: true -require 'bundler/gem_tasks' -require 'rake/testtask' +require "bundler/gem_tasks" +require "rake/testtask" Rake::TestTask.new(:test) do |t| - t.libs << 'test' - t.libs << 'lib' - t.test_files = FileList['test/**/*_test.rb'] + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/**/*_test.rb"] end task default: :test + +FILEPATHS = %w[ + Gemfile + Rakefile + syntax_tree.gemspec + lib/**/*.rb + test/*.rb +].freeze + +task :syntax_tree do + $:.unshift File.expand_path("lib", __dir__) + require "syntax_tree" + require "syntax_tree/cli" +end + +task check: :syntax_tree do + exit SyntaxTree::CLI.run(["check"] + FILEPATHS) +end + +task format: :syntax_tree do + exit SyntaxTree::CLI.run(["write"] + FILEPATHS) +end diff --git a/config/rubocop.yml b/config/rubocop.yml new file mode 100644 index 00000000..eff5aede --- /dev/null +++ b/config/rubocop.yml @@ -0,0 +1,64 @@ +# Disabling all Layout/* rules, as they're unnecessary when the user is using +# Syntax Tree to handle all of the formatting. +Layout: + Enabled: false + +# Re-enable Layout/LineLength because certain cops that most projects use +# (e.g. Style/IfUnlessModifier) require Layout/LineLength to be enabled. +# By leaving it disabled, those rules will mis-fire. +# +# Users can always override these defaults in their own rubocop.yml files. +# https://github.com/prettier/plugin-ruby/issues/825 +Layout/LineLength: + Enabled: true + +Style/MultilineIfModifier: + Enabled: false + +# Syntax Tree will expand empty methods to put the end keyword on the subsequent +# line to reduce git diff noise. +Style/EmptyMethod: + EnforcedStyle: expanded + +# lambdas that are constructed with the lambda method call cannot be safely +# turned into lambda literals without removing a method call. +Style/Lambda: + Enabled: false + +# When method chains with multiple blocks are chained together, rubocop will let +# them pass if they're using braces but not if they're using do and end +# keywords. Because we will break individual blocks down to using keywords if +# they are multiline, this conflicts with rubocop. +Style/MultilineBlockChain: + Enabled: false + +# Syntax Tree by default uses double quotes, so changing the configuration here +# to match that. +Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + EnforcedStyle: double_quotes + +Style/QuotedSymbols: + EnforcedStyle: double_quotes + +# We let users have a little more freedom with symbol and words arrays. If the +# user only has an individual item like ["value"] then we don't bother +# converting it because it ends up being just noise. +Style/SymbolArray: + Enabled: false + +Style/WordArray: + Enabled: false + +# We don't support trailing commas in Syntax Tree by default, so just turning +# these off for now. +Style/TrailingCommaInArguments: + Enabled: false + +Style/TrailingCommaInArrayLiteral: + Enabled: false + +Style/TrailingCommaInHashLiteral: + Enabled: false diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 632b062e..1f3b4f11 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -30,6 +30,10 @@ end end +# 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. module SyntaxTree # This holds references to objects that respond to both #parse and #format # so that we can use them in the CLI. diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index f799fcb4..3efa79cb 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module SyntaxTree + # Syntax Tree ships with the `stree` CLI, which can be used to inspect and + # manipulate Ruby code. This module is responsible for powering that CLI. module CLI # A utility wrapper around colored strings in the output. class Color @@ -46,7 +48,7 @@ def failure # An action of the CLI that prints out the AST for the given source. class AST < Action - def run(handler, filepath, source) + def run(handler, _filepath, source) pp handler.parse(source) end end @@ -83,9 +85,7 @@ def run(handler, filepath, source) warning = "[#{Color.yellow("warn")}] #{filepath}" formatted = handler.format(source) - if formatted != handler.format(formatted) - raise NonIdempotentFormatError - end + raise NonIdempotentFormatError if formatted != handler.format(formatted) rescue StandardError warn(warning) raise @@ -102,7 +102,7 @@ def failure # An action of the CLI that prints out the doc tree IR for the given source. class Doc < Action - def run(handler, filepath, source) + def run(handler, _filepath, source) formatter = Formatter.new(source, []) handler.parse(source).format(formatter) pp formatter.groups.first @@ -111,7 +111,7 @@ def run(handler, filepath, source) # An action of the CLI that formats the input source and prints it out. class Format < Action - def run(handler, filepath, source) + def run(handler, _filepath, source) puts handler.format(source) end end @@ -119,7 +119,7 @@ def run(handler, filepath, source) # An action of the CLI that converts the source into its equivalent JSON # representation. class Json < Action - def run(handler, filepath, source) + def run(handler, _filepath, source) object = Visitor::JSONVisitor.new.visit(handler.parse(source)) puts JSON.pretty_generate(object) end @@ -128,7 +128,7 @@ def run(handler, filepath, source) # An action of the CLI that outputs a pattern-matching Ruby expression that # would match the input given. class Match < Action - def run(handler, filepath, source) + def run(handler, _filepath, source) formatter = Formatter.new(source, []) Visitor::MatchVisitor.new(formatter).visit(handler.parse(source)) formatter.flush @@ -242,7 +242,7 @@ def run(argv) # 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? + if $stdin.tty? && arguments.empty? warn(HELP) return 1 end @@ -280,7 +280,7 @@ def run(argv) errored = true rescue Check::UnformattedError, Debug::NonIdempotentFormatError errored = true - rescue => error + rescue StandardError => error warn(error.message) warn(error.backtrace) errored = true @@ -298,18 +298,20 @@ def run(argv) private def each_file(arguments) - if STDIN.tty? + if $stdin.tty? || arguments.any? 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 + 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 + yield HANDLERS[".rb"], :stdin, $stdin.read end end diff --git a/lib/syntax_tree/formatter.rb b/lib/syntax_tree/formatter.rb index 7015a837..9959421a 100644 --- a/lib/syntax_tree/formatter.rb +++ b/lib/syntax_tree/formatter.rb @@ -41,11 +41,12 @@ def format(node, stackable: true) # If the node has a stree-ignore comment right before it, then we're # going to just print out the node as it was seen in the source. - if leading.last&.ignore? - doc = text(source[node.location.start_char...node.location.end_char]) - else - doc = node.format(self) - end + doc = + if leading.last&.ignore? + text(source[node.location.start_char...node.location.end_char]) + else + node.format(self) + end # Print all comments that were found after the node. trailing.each do |comment| diff --git a/lib/syntax_tree/language_server.rb b/lib/syntax_tree/language_server.rb index 177a39a1..1e305cca 100644 --- a/lib/syntax_tree/language_server.rb +++ b/lib/syntax_tree/language_server.rb @@ -7,10 +7,15 @@ require_relative "language_server/inlay_hints" module SyntaxTree + # Syntax Tree additionally ships with a language server conforming to the + # language server protocol. It can be invoked through the CLI by running: + # + # stree lsp + # class LanguageServer attr_reader :input, :output - def initialize(input: STDIN, output: STDOUT) + def initialize(input: $stdin, output: $stdout) @input = input.binmode @output = output.binmode end @@ -21,7 +26,7 @@ def run hash[uri] = File.binread(CGI.unescape(URI.parse(uri).path)) end - while headers = input.gets("\r\n\r\n") + while (headers = input.gets("\r\n\r\n")) source = input.read(headers[/Content-Length: (\d+)/i, 1].to_i) request = JSON.parse(source, symbolize_names: true) @@ -29,26 +34,46 @@ def run in { method: "initialize", id: } store.clear write(id: id, result: { capabilities: capabilities }) - in { method: "initialized" } + in method: "initialized" # ignored - in { method: "shutdown" } + in method: "shutdown" store.clear return - in { method: "textDocument/didChange", params: { textDocument: { uri: }, contentChanges: [{ text: }, *] } } + in { + method: "textDocument/didChange", + params: { textDocument: { uri: }, contentChanges: [{ text: }, *] } + } store[uri] = text - in { method: "textDocument/didOpen", params: { textDocument: { uri:, text: } } } + in { + method: "textDocument/didOpen", + params: { textDocument: { uri:, text: } } + } store[uri] = text - in { method: "textDocument/didClose", params: { textDocument: { uri: } } } + in { + method: "textDocument/didClose", params: { textDocument: { uri: } } + } store.delete(uri) - in { method: "textDocument/formatting", id:, params: { textDocument: { uri: } } } + in { + method: "textDocument/formatting", + id:, + params: { textDocument: { uri: } } + } write(id: id, result: [format(store[uri])]) - in { method: "textDocument/inlayHints", id:, params: { textDocument: { uri: } } } + in { + method: "textDocument/inlayHints", + id:, + params: { textDocument: { uri: } } + } write(id: id, result: inlay_hints(store[uri])) - in { method: "syntaxTree/visualizing", id:, params: { textDocument: { uri: } } } + in { + method: "syntaxTree/visualizing", + id:, + params: { textDocument: { uri: } } + } output = [] PP.pp(SyntaxTree.parse(store[uri]), output) write(id: id, result: output.join) - in { method: %r{\$/.+} } + in method: %r{\$/.+} # ignored else raise "Unhandled: #{request}" @@ -61,15 +86,24 @@ def run def capabilities { documentFormattingProvider: true, - textDocumentSync: { change: 1, openClose: true } + textDocumentSync: { + change: 1, + openClose: true + } } end def format(source) { range: { - start: { line: 0, character: 0 }, - end: { line: source.lines.size + 1, character: 0 } + start: { + line: 0, + character: 0 + }, + end: { + line: source.lines.size + 1, + character: 0 + } }, newText: SyntaxTree.format(source) } @@ -88,6 +122,8 @@ def inlay_hints(source) after: inlay_hints.after.map(&serialize) } rescue Parser::ParseError + # If there is a parse error, then we're not going to return any inlay + # hints for this source. end def write(value) diff --git a/lib/syntax_tree/language_server/inlay_hints.rb b/lib/syntax_tree/language_server/inlay_hints.rb index 02cbc6f5..69fc5ce4 100644 --- a/lib/syntax_tree/language_server/inlay_hints.rb +++ b/lib/syntax_tree/language_server/inlay_hints.rb @@ -2,6 +2,13 @@ module SyntaxTree class LanguageServer + # This class provides inlay hints for the language server. It is loosely + # designed around the LSP spec, but existed before the spec was finalized so + # is a little different for now. + # + # For more information, see the spec here: + # https://github.com/microsoft/language-server-protocol/issues/956. + # class InlayHints < Visitor attr_reader :stack, :before, :after diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 0a4c0fdf..e5aa6b50 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -3,9 +3,21 @@ module SyntaxTree # Represents the location of a node in the tree from the source code. class Location - attr_reader :start_line, :start_char, :start_column, :end_line, :end_char, :end_column + attr_reader :start_line, + :start_char, + :start_column, + :end_line, + :end_char, + :end_column - def initialize(start_line:, start_char:, start_column:, end_line:, end_char:, end_column:) + 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 @@ -39,7 +51,7 @@ def deconstruct [start_line, start_char, start_column, end_line, end_char, end_column] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { start_line: start_line, start_char: start_char, @@ -62,7 +74,14 @@ def self.token(line:, char:, column:, size:) end 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) + new( + start_line: line, + start_char: char, + start_column: column, + end_line: line, + end_char: char, + end_column: column + ) end end @@ -140,7 +159,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { lbrace: lbrace, statements: statements, @@ -192,7 +211,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -243,7 +262,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { lbrace: lbrace, statements: statements, @@ -298,7 +317,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -323,6 +342,8 @@ def format(q) # symbols (note that this includes dynamic symbols like # :"left-#{middle}-right"). class Alias < Node + # Formats an argument to the alias keyword. For symbol literals it uses the + # value of the symbol directly to look like bare words. class AliasArgumentFormatter # [DynaSymbol | SymbolLiteral] the argument being passed to alias attr_reader :argument @@ -374,7 +395,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { left: left, right: right, location: location, comments: comments } end @@ -435,7 +456,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { collection: collection, index: index, @@ -496,7 +517,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { collection: collection, index: index, @@ -558,7 +579,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { arguments: arguments, location: location, comments: comments } end @@ -606,7 +627,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parts: parts, location: location, comments: comments } end @@ -642,7 +663,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -679,7 +700,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -729,7 +750,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -745,6 +766,7 @@ def format(q) # [one, two, three] # class ArrayLiteral < Node + # Formats an array of multiple simple string literals into the %w syntax. class QWordsFormatter # [Args] the contents of the array attr_reader :contents @@ -761,7 +783,7 @@ def format(q) if part.is_a?(StringLiteral) q.format(part.parts.first) else - q.text(part.value[1..-1]) + q.text(part.value[1..]) end end end @@ -770,6 +792,7 @@ def format(q) end end + # Formats an array of multiple simple symbol literals into the %i syntax. class QSymbolsFormatter # [Args] the contents of the array attr_reader :contents @@ -791,6 +814,28 @@ def format(q) end end + # Formats an array that contains only a list of variable references. To make + # things simpler, if there are a bunch, we format them all using the "fill" + # algorithm as opposed to breaking them into a ton of lines. For example, + # + # [foo, bar, baz] + # + # instead of becoming: + # + # [ + # foo, + # bar, + # baz + # ] + # + # would instead become: + # + # [ + # foo, bar, + # baz + # ] + # + # provided the line length was hit between `bar` and `baz`. class VarRefsFormatter # [Args] the contents of the array attr_reader :contents @@ -816,6 +861,9 @@ def format(q) end end + # This is a special formatter used if the array literal contains no values + # but _does_ contain comments. In this case we do some special formatting to + # make sure the comments gets indented properly. class EmptyWithCommentsFormatter # [LBracket] the opening bracket attr_reader :lbracket @@ -865,7 +913,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { lbracket: lbracket, contents: contents, @@ -951,7 +999,8 @@ def var_refs?(q) # If we have an empty array that contains only comments, then we're going # to do some special printing to ensure they get indented correctly. def empty_with_comments? - contents.nil? && lbracket.comments.any? && lbracket.comments.none?(&:inline?) + contents.nil? && lbracket.comments.any? && + lbracket.comments.none?(&:inline?) end end @@ -973,6 +1022,7 @@ def empty_with_comments? # and an optional array of positional matches that occur after the splat. # All of the in clauses above would create an AryPtn node. class AryPtn < Node + # Formats the optional splat of an array pattern. class RestFormatter # [VarField] the identifier that represents the remaining positionals attr_reader :value @@ -1035,7 +1085,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { constant: constant, requireds: requireds, @@ -1067,6 +1117,8 @@ def format(q) q.text("[") q.seplist(parts) { |part| q.format(part) } q.text("]") + elsif parts.empty? + q.text("[]") else q.group { q.seplist(parts) { |part| q.format(part) } } end @@ -1077,7 +1129,8 @@ def format(q) module AssignFormatting def self.skip_indent?(value) case value - in ArrayLiteral | HashLiteral | Heredoc | Lambda | QSymbols | QWords | Symbols | Words + in ArrayLiteral | HashLiteral | Heredoc | Lambda | QSymbols | QWords | + Symbols | Words true in Call[receiver:] skip_indent?(receiver) @@ -1123,7 +1176,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { target: target, value: value, location: location, comments: comments } end @@ -1185,12 +1238,12 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { key: key, value: value, location: location, comments: comments } end def format(q) - if value&.is_a?(HashLiteral) + if value.is_a?(HashLiteral) format_contents(q) else q.group { format_contents(q) } @@ -1243,7 +1296,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -1281,7 +1334,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -1316,7 +1369,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -1329,6 +1382,7 @@ def format(q) # hash or bare hash. It first determines if every key in the hash can use # labels. If it can, it uses labels. Otherwise it uses hash rockets. module HashKeyFormatter + # Formats the keys of a hash literal using labels. class Labels def format_key(q, key) case key @@ -1344,6 +1398,7 @@ def format_key(q, key) end end + # Formats the keys of a hash literal using hash rockets. class Rockets def format_key(q, key) case key @@ -1417,7 +1472,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { assocs: assocs, location: location, comments: comments } end @@ -1459,7 +1514,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { bodystmt: bodystmt, location: location, comments: comments } end @@ -1507,7 +1562,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statement: statement, location: location, comments: comments } end @@ -1567,7 +1622,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { left: left, operator: operator, @@ -1682,7 +1737,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { params: params, locals: locals, location: location, comments: comments } end @@ -1726,7 +1781,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name, location: location, comments: comments } end @@ -1822,7 +1877,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statements: statements, rescue_clause: rescue_clause, @@ -1868,6 +1923,7 @@ def format(q) # Responsible for formatting either a BraceBlock or a DoBlock. class BlockFormatter + # Formats the opening brace or keyword of a block. class BlockOpenFormatter # [String] the actual output that should be printed attr_reader :text @@ -1933,9 +1989,9 @@ def format(q) end q.group do - q.if_break { format_break(q, break_opening, break_closing) }.if_flat do - format_flat(q, flat_opening, flat_closing) - end + q + .if_break { format_break(q, break_opening, break_closing) } + .if_flat { format_flat(q, flat_opening, flat_closing) } end end @@ -2060,7 +2116,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { lbrace: lbrace, block_var: block_var, @@ -2099,7 +2155,11 @@ def format(q) # # break # - in [Paren[contents: { body: [ArrayLiteral[contents: { parts: [_, _, *] }] => array] }]] + in [Paren[ + contents: { + body: [ArrayLiteral[contents: { parts: [_, _, *] }] => array] + } + ]] # Here we have a single argument that is a set of parentheses wrapping # an array literal that has at least 2 elements. We're going to print # the contents of the array directly. This would be like if we had: @@ -2255,7 +2315,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { arguments: arguments, location: location, comments: comments } end @@ -2287,6 +2347,20 @@ def format(q) end end + # This is probably the most complicated formatter in this file. It's + # responsible for formatting chains of method calls, with or without arguments + # or blocks. In general, we want to go from something like + # + # foo.bar.baz + # + # to + # + # foo + # .bar + # .baz + # + # Of course there are a lot of caveats to that, including trailing operators + # when necessary, where comments are places, how blocks are aligned, etc. class CallChainFormatter # [Call | MethodAddBlock] the top of the call chain attr_reader :node @@ -2301,7 +2375,7 @@ def format(q) # First, walk down the chain until we get to the point where we're not # longer at a chainable node. - while true + loop do case children.last in Call[receiver: Call] children << children.last.receiver @@ -2321,7 +2395,7 @@ def format(q) # block. For more details, see # https://github.com/prettier/plugin-ruby/issues/863. parents = q.parents.take(4) - if parent = parents[2] + if (parent = parents[2]) # If we're at a do_block, then we want to go one more level up. This is # because do blocks have BodyStmt nodes instead of just Statements # nodes. @@ -2335,7 +2409,11 @@ def format(q) end if children.length >= threshold - q.group { q.if_break { format_chain(q, children) }.if_flat { node.format_contents(q) } } + q.group do + q + .if_break { format_chain(q, children) } + .if_flat { node.format_contents(q) } + end else node.format_contents(q) end @@ -2346,9 +2424,9 @@ def format_chain(q, children) # chain of calls without arguments except for the last one. This is common # enough in Ruby source code that it's worth the extra complexity here. empty_except_last = - children.drop(1).all? do |child| - child.is_a?(Call) && child.arguments.nil? - end + children + .drop(1) + .all? { |child| child.is_a?(Call) && child.arguments.nil? } # Here, we're going to add all of the children onto the stack of the # formatter so it's as if we had descending normally into them. This is @@ -2368,9 +2446,12 @@ def format_chain(q, children) # and a trailing operator. skip_operator = false - while child = children.pop + while (child = children.pop) case child - in Call[receiver: Call[message: { value: "where" }], message: { value: "not" }] + in Call[ + receiver: Call[message: { value: "where" }], + message: { value: "not" } + ] # This is very specialized behavior wherein we group # .where.not calls together because it looks better. For more # information, see @@ -2432,16 +2513,25 @@ def self.chained?(node) # want to indent the first call. So we'll pop off the first children and # format it separately here. def attach_directly?(node) - [ArrayLiteral, HashLiteral, Heredoc, If, Unless, XStringLiteral] - .include?(node.receiver.class) + [ArrayLiteral, HashLiteral, Heredoc, If, Unless, XStringLiteral].include?( + node.receiver.class + ) end - def format_child(q, child, skip_comments: false, skip_operator: false, skip_attached: false) + def format_child( + q, + child, + skip_comments: false, + skip_operator: false, + skip_attached: false + ) # First, format the actual contents of the child. case child in Call q.group do - q.format(CallOperatorFormatter.new(child.operator)) unless skip_operator + unless skip_operator + q.format(CallOperatorFormatter.new(child.operator)) + end q.format(child.message) if child.message != :call child.format_arguments(q) unless skip_attached end @@ -2513,7 +2603,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { receiver: receiver, operator: operator, @@ -2528,8 +2618,13 @@ def format(q) # If we're at the top of a call chain, then we're going to do some # specialized printing in case we can print it nicely. We _only_ do this # at the top of the chain to avoid weird recursion issues. - if !CallChainFormatter.chained?(q.parent) && CallChainFormatter.chained?(receiver) - q.group { q.if_break { CallChainFormatter.new(self).format(q) }.if_flat { format_contents(q) } } + if !CallChainFormatter.chained?(q.parent) && + CallChainFormatter.chained?(receiver) + q.group do + q + .if_break { CallChainFormatter.new(self).format(q) } + .if_flat { format_contents(q) } + end else format_contents(q) end @@ -2618,7 +2713,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { keyword: keyword, value: value, @@ -2683,7 +2778,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, operator: operator, @@ -2772,7 +2867,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { constant: constant, superclass: superclass, @@ -2837,7 +2932,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -2875,7 +2970,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { message: message, arguments: arguments, @@ -2957,7 +3052,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { receiver: receiver, operator: operator, @@ -3079,7 +3174,7 @@ def trailing? end def ignore? - value[1..-1].strip == "stree-ignore" + value[1..].strip == "stree-ignore" end def comments @@ -3096,7 +3191,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, inline: inline, location: location } end @@ -3142,7 +3237,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -3184,7 +3279,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parent: parent, constant: constant, @@ -3231,7 +3326,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parent: parent, constant: constant, @@ -3276,7 +3371,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { constant: constant, location: location, comments: comments } end @@ -3312,7 +3407,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -3356,7 +3451,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name, params: params, @@ -3441,7 +3536,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { target: target, operator: operator, @@ -3509,7 +3604,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -3575,7 +3670,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { target: target, operator: operator, @@ -3650,7 +3745,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { keyword: keyword, block_var: block_var, @@ -3730,7 +3825,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { left: left, right: right, location: location, comments: comments } end @@ -3778,7 +3873,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { left: left, right: right, location: location, comments: comments } end @@ -3800,7 +3895,7 @@ module Quotes # quotes, then single quotes would deactivate it.) def self.locked?(node) node.parts.any? do |part| - part.is_a?(TStringContent) && part.value.match?(/#[@${]|[\\]/) + part.is_a?(TStringContent) && part.value.match?(/\\|#[@${]/) end end @@ -3865,7 +3960,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parts: parts, quote: quote, location: location, comments: comments } end @@ -3906,9 +4001,14 @@ def quotes(q) matching = Quotes.matching(quote[2]) pattern = /[\n#{Regexp.escape(matching)}'"]/ - if parts.any? { |part| - part.is_a?(TStringContent) && part.value.match?(pattern) - } + # This check is to ensure we don't find a matching quote inside of the + # symbol that would be confusing. + matched = + parts.any? do |part| + part.is_a?(TStringContent) && part.value.match?(pattern) + end + + if matched [quote, matching] elsif Quotes.locked?(self) ["#{":" unless hash_key}'", "'"] @@ -3917,7 +4017,7 @@ def quotes(q) end elsif Quotes.locked?(self) if quote.start_with?(":") - [hash_key ? quote[1..-1] : quote, quote[1..-1]] + [hash_key ? quote[1..] : quote, quote[1..]] else [hash_key ? quote : ":#{quote}", quote] end @@ -3960,7 +4060,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { keyword: keyword, statements: statements, @@ -4026,7 +4126,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { predicate: predicate, statements: statements, @@ -4098,7 +4198,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end @@ -4133,7 +4233,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -4163,7 +4263,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -4195,7 +4295,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -4234,7 +4334,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { keyword: keyword, statements: statements, @@ -4288,7 +4388,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -4331,7 +4431,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, arguments: arguments, @@ -4343,7 +4443,8 @@ def deconstruct_keys(keys) def format(q) q.format(value) - if arguments.is_a?(ArgParen) && arguments.arguments.nil? && !value.is_a?(Const) + if arguments.is_a?(ArgParen) && arguments.arguments.nil? && + !value.is_a?(Const) # If you're using an explicit set of parentheses on something that looks # like a constant, then we need to match that in order to maintain valid # Ruby. For example, you could do something like Foo(), on which we @@ -4390,7 +4491,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parent: parent, operator: operator, @@ -4436,7 +4537,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -4488,7 +4589,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { constant: constant, left: left, @@ -4552,7 +4653,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index, collection: collection, @@ -4609,7 +4710,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -4623,6 +4724,9 @@ def format(q) # { key => value } # class HashLiteral < Node + # This is a special formatter used if the hash literal contains no values + # but _does_ contain comments. In this case we do some special formatting to + # make sure the comments gets indented properly. class EmptyWithCommentsFormatter # [LBrace] the opening brace attr_reader :lbrace @@ -4672,7 +4776,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { lbrace: lbrace, assocs: assocs, location: location, comments: comments } end @@ -4741,7 +4845,14 @@ class Heredoc < Node # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(beginning:, ending: nil, dedent: 0, parts: [], location:, comments: []) + def initialize( + beginning:, + ending: nil, + dedent: 0, + parts: [], + location:, + comments: [] + ) @beginning = beginning @ending = ending @dedent = dedent @@ -4760,7 +4871,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { beginning: beginning, location: location, @@ -4775,8 +4886,12 @@ def format(q) # prettyprint module. It's when you want to force a newline, but don't # want to force the break parent. breakable = -> do - q.target << - PrettyPrint::Breakable.new(" ", 1, indent: false, force: true) + q.target << PrettyPrint::Breakable.new( + " ", + 1, + indent: false, + force: true + ) end q.group do @@ -4832,7 +4947,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -4849,6 +4964,7 @@ def format(q) # end # class HshPtn < Node + # Formats a key-value pair in a hash pattern. The value is optional. class KeywordFormatter # [Label] the keyword being used attr_reader :key @@ -4875,6 +4991,7 @@ def format(q) end end + # Formats the optional double-splat from the pattern. class KeywordRestFormatter # [VarField] the parameter that matches the remaining keywords attr_reader :keyword_rest @@ -4924,7 +5041,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { constant: constant, keywords: keywords, @@ -4947,27 +5064,52 @@ def format(q) q.text(" then") if !constant && keyword_rest && keyword_rest.value.nil? end + # If there is a constant, we're going to format to have the constant name + # first and then use brackets. if constant - q.format(constant) - q.group(0, "[", "]", &contents) + q.group do + q.format(constant) + q.text("[") + q.indent do + q.breakable("") + contents.call + end + q.breakable("") + q.text("]") + end return end + # If there's nothing at all, then we're going to use empty braces. if parts.empty? q.text("{}") - elsif PATTERNS.include?(q.parent.class) - q.text("{ ") - contents.call - q.text(" }") - else + return + end + + # If there's only one pair, then we'll just print the contents provided + # we're not inside another pattern. + if !PATTERNS.include?(q.parent.class) && parts.size == 1 contents.call + return + end + + # Otherwise, we're going to always use braces to make it clear it's a hash + # pattern. + q.group do + q.text("{") + q.indent do + q.breakable + contents.call + end + q.breakable + q.text("}") end end end # The list of nodes that represent patterns inside of pattern matching so that # when a pattern is being printed it knows if it's nested. - PATTERNS = [AryPtn, Binary, FndPtn, HshPtn, RAssign] + PATTERNS = [AryPtn, Binary, FndPtn, HshPtn, RAssign].freeze # Ident represents an identifier anywhere in code. It can represent a very # large number of things, depending on where it is in the syntax tree. @@ -4997,7 +5139,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -5014,7 +5156,7 @@ module ContainsAssignment def self.call(parent) queue = [parent] - while node = queue.shift + while (node = queue.shift) return true if [Assign, MAssign, OpAssign].include?(node.class) queue += node.child_nodes end @@ -5041,9 +5183,12 @@ def call(q, node) else # Otherwise, we're going to check the conditional for certain cases. case node - in { predicate: Assign | Command | CommandCall | MAssign | OpAssign } + in predicate: Assign | Command | CommandCall | MAssign | OpAssign false - in { statements: { body: [truthy] }, consequent: Else[statements: { body: [falsy] }] } + in { + statements: { body: [truthy] }, + consequent: Else[statements: { body: [falsy] }] + } ternaryable?(truthy) && ternaryable?(falsy) else false @@ -5054,8 +5199,8 @@ def call(q, node) private # Certain expressions cannot be reduced to a ternary without adding - # parentheses around them. In this case we say they cannot be ternaried and - # default instead to breaking them into multiple lines. + # parentheses around them. In this case we say they cannot be ternaried + # and default instead to breaking them into multiple lines. def ternaryable?(statement) # This is a list of nodes that should not be allowed to be a part of a # ternary clause. @@ -5113,13 +5258,15 @@ def format(q) q.group { format_break(q, force: true) } else q.group do - q.if_break { format_break(q, force: false) }.if_flat do - Parentheses.flat(q) do - q.format(node.statements) - q.text(" #{keyword} ") - q.format(node.predicate) + q + .if_break { format_break(q, force: false) } + .if_flat do + Parentheses.flat(q) do + q.format(node.statements) + q.text(" #{keyword} ") + q.format(node.predicate) + end end - end end end end @@ -5148,51 +5295,53 @@ def format_break(q, force:) def format_ternary(q) q.group do - q.if_break do - q.text("#{keyword} ") - q.nest(keyword.length + 1) { q.format(node.predicate) } - - q.indent do - q.breakable - q.format(node.statements) - end + q + .if_break do + q.text("#{keyword} ") + q.nest(keyword.length + 1) { q.format(node.predicate) } - q.breakable - q.group do - q.format(node.consequent.keyword) q.indent do - # This is a very special case of breakable where we want to force - # it into the output but we _don't_ want to explicitly break the - # parent. If a break-parent shows up in the tree, then it's going - # to force it all the way up to the tree, which is going to negate - # the ternary. Maybe this should be an option in prettyprint? As - # in force: :no_break_parent or something. - q.target << PrettyPrint::Breakable.new(" ", 1, force: true) - q.format(node.consequent.statements) + q.breakable + q.format(node.statements) end - end - q.breakable - q.text("end") - end.if_flat do - Parentheses.flat(q) do - q.format(node.predicate) - q.text(" ? ") + q.breakable + q.group do + q.format(node.consequent.keyword) + q.indent do + # This is a very special case of breakable where we want to + # force it into the output but we _don't_ want to explicitly + # break the parent. If a break-parent shows up in the tree, then + # it's going to force it all the way up to the tree, which is + # going to negate the ternary. Maybe this should be an option in + # prettyprint? As in force: :no_break_parent or something. + q.target << PrettyPrint::Breakable.new(" ", 1, force: true) + q.format(node.consequent.statements) + end + end - statements = [node.statements, node.consequent.statements] - statements.reverse! if keyword == "unless" + q.breakable + q.text("end") + end + .if_flat do + Parentheses.flat(q) do + q.format(node.predicate) + q.text(" ? ") + + statements = [node.statements, node.consequent.statements] + statements.reverse! if keyword == "unless" - q.format(statements[0]) - q.text(" : ") - q.format(statements[1]) + q.format(statements[0]) + q.text(" : ") + q.format(statements[1]) + end end - end end end def contains_conditional? case node - in { statements: { body: [If | IfMod | IfOp | Unless | UnlessMod] } } + in statements: { body: [If | IfMod | IfOp | Unless | UnlessMod] } true else false @@ -5242,7 +5391,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { predicate: predicate, statements: statements, @@ -5292,7 +5441,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { predicate: predicate, truthy: truthy, @@ -5310,7 +5459,8 @@ def format(q) Yield0, ZSuper ] - if q.parent.is_a?(Paren) || force_flat.include?(truthy.class) || force_flat.include?(falsy.class) + if q.parent.is_a?(Paren) || force_flat.include?(truthy.class) || + force_flat.include?(falsy.class) q.group { format_flat(q) } return end @@ -5430,7 +5580,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statement: statement, predicate: predicate, @@ -5471,7 +5621,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -5518,7 +5668,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { pattern: pattern, statements: statements, @@ -5577,7 +5727,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -5587,7 +5737,7 @@ def format(q) # the values, then we're going to insert them every 3 characters # starting from the right. index = (value.length + 2) % 3 - q.text(" #{value}"[index..-1].scan(/.../).join("_").strip) + q.text(" #{value}"[index..].scan(/.../).join("_").strip) else q.text(value) end @@ -5621,7 +5771,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -5666,7 +5816,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -5703,7 +5853,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name, location: location, comments: comments } end @@ -5749,7 +5899,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -5784,7 +5934,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -5820,7 +5970,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { params: params, statements: statements, @@ -5842,25 +5992,27 @@ def format(q) end q.text(" ") - q.if_break do - force_parens = - q.parents.any? do |node| - node.is_a?(Command) || node.is_a?(CommandCall) + q + .if_break do + force_parens = + q.parents.any? do |node| + node.is_a?(Command) || node.is_a?(CommandCall) + end + + q.text(force_parens ? "{" : "do") + q.indent do + q.breakable + q.format(statements) end - q.text(force_parens ? "{" : "do") - q.indent do q.breakable + q.text(force_parens ? "}" : "end") + end + .if_flat do + q.text("{ ") q.format(statements) + q.text(" }") end - - q.breakable - q.text(force_parens ? "}" : "end") - end.if_flat do - q.text("{ ") - q.format(statements) - q.text(" }") - end end end end @@ -5889,7 +6041,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -5922,7 +6074,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -5955,7 +6107,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -6005,7 +6157,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { target: target, value: value, location: location, comments: comments } end @@ -6052,7 +6204,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { call: call, block: block, location: location, comments: comments } end @@ -6060,8 +6212,13 @@ def format(q) # If we're at the top of a call chain, then we're going to do some # specialized printing in case we can print it nicely. We _only_ do this # at the top of the chain to avoid weird recursion issues. - if !CallChainFormatter.chained?(q.parent) && CallChainFormatter.chained?(call) - q.group { q.if_break { CallChainFormatter.new(self).format(q) }.if_flat { format_contents(q) } } + if !CallChainFormatter.chained?(q.parent) && + CallChainFormatter.chained?(call) + q.group do + q + .if_break { CallChainFormatter.new(self).format(q) } + .if_flat { format_contents(q) } + end else format_contents(q) end @@ -6108,7 +6265,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parts: parts, location: location, comma: comma, comments: comments } end @@ -6152,7 +6309,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { contents: contents, location: location, comments: comments } end @@ -6208,7 +6365,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { constant: constant, bodystmt: bodystmt, @@ -6275,7 +6432,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parts: parts, location: location, comments: comments } end @@ -6324,7 +6481,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { arguments: arguments, location: location, comments: comments } end @@ -6361,7 +6518,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -6407,7 +6564,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { target: target, operator: operator, @@ -6475,7 +6632,16 @@ def skip_indent? # This approach maintains the nice conciseness of the inline version, while # keeping the correct semantic meaning. module Parentheses - NODES = [Args, Assign, Assoc, Binary, Call, Defined, MAssign, OpAssign] + NODES = [ + Args, + Assign, + Assoc, + Binary, + Call, + Defined, + MAssign, + OpAssign + ].freeze def self.flat(q) return yield unless NODES.include?(q.parent.class) @@ -6507,6 +6673,8 @@ def self.break(q) # def method(param) end # class Params < Node + # Formats the optional position of the parameters. This includes the label, + # as well as the default value. class OptionalFormatter # [Ident] the name of the parameter attr_reader :name @@ -6530,6 +6698,8 @@ def format(q) end end + # Formats the keyword position of the parameters. This includes the label, + # as well as an optional default value. class KeywordFormatter # [Ident] the name of the parameter attr_reader :name @@ -6556,6 +6726,8 @@ def format(q) end end + # Formats the keyword_rest position of the parameters. This can be the **nil + # syntax, the ... syntax, or the ** syntax. class KeywordRestFormatter # [:nil | ArgsForward | KwRestParam] the value of the parameter attr_reader :value @@ -6569,11 +6741,7 @@ def comments end def format(q) - if value == :nil - q.text("**nil") - else - q.format(value) - end + value == :nil ? q.text("**nil") : q.format(value) end end @@ -6654,7 +6822,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { location: location, requireds: requireds, @@ -6675,18 +6843,17 @@ def format(q) ] parts << rest if rest && !rest.is_a?(ExcessedComma) - parts += - [ - *posts, - *keywords.map { |(name, value)| KeywordFormatter.new(name, value) } - ] + parts += [ + *posts, + *keywords.map { |(name, value)| KeywordFormatter.new(name, value) } + ] parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest parts << block if block contents = -> do q.seplist(parts) { |part| q.format(part) } - q.format(rest) if rest && rest.is_a?(ExcessedComma) + q.format(rest) if rest.is_a?(ExcessedComma) end if ![Def, Defs, DefEndless].include?(q.parent.class) || parts.empty? @@ -6736,7 +6903,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { lparen: lparen, contents: contents, @@ -6787,7 +6954,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -6820,7 +6987,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statements: statements, location: location, comments: comments } end @@ -6865,7 +7032,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { beginning: beginning, elements: elements, @@ -6920,7 +7087,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end @@ -6965,7 +7132,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { beginning: beginning, elements: elements, @@ -7020,7 +7187,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -7052,7 +7219,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -7081,7 +7248,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -7106,7 +7273,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -7138,7 +7305,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -7177,7 +7344,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { beginning: beginning, parts: parts, location: location } end end @@ -7210,7 +7377,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -7244,7 +7411,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -7285,7 +7452,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { beginning: beginning, ending: ending, @@ -7296,7 +7463,7 @@ def deconstruct_keys(keys) end def format(q) - braces = ambiguous?(q) || include?(%r{\/}) + braces = ambiguous?(q) || include?(%r{/}) if braces && include?(/[{}]/) q.group do @@ -7323,14 +7490,14 @@ def format(q) end q.text("}") - q.text(ending[1..-1]) + q.text(ending[1..]) end else q.group do q.text("/") q.format_each(parts) q.text("/") - q.text(ending[1..-1]) + q.text(ending[1..]) end end end @@ -7390,7 +7557,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { exceptions: exceptions, variable: variable, @@ -7465,7 +7632,10 @@ def bind_end(end_char, end_column) if consequent consequent.bind_end(end_char, end_column) - statements.bind_end(consequent.location.start_char, consequent.location.start_column) + statements.bind_end( + consequent.location.start_char, + consequent.location.start_column + ) else statements.bind_end(end_char, end_column) end @@ -7481,7 +7651,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { keyword: keyword, exception: exception, @@ -7548,7 +7718,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statement: statement, value: value, @@ -7602,7 +7772,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name, location: location, comments: comments } end @@ -7639,7 +7809,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -7675,7 +7845,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { arguments: arguments, location: location, comments: comments } end @@ -7711,7 +7881,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -7740,7 +7910,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -7779,7 +7949,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { target: target, bodystmt: bodystmt, @@ -7881,7 +8051,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parser: parser, body: body, location: location, comments: comments } end @@ -7951,12 +8121,19 @@ def attach_comments(start_char, end_char) comment = parser_comments[comment_index] location = comment.location - if !comment.inline? && (start_char <= location.start_char) && (end_char >= location.end_char) && !comment.ignore? - while (node = body[body_index]) && (node.is_a?(VoidStmt) || node.location.start_char < location.start_char) + if !comment.inline? && (start_char <= location.start_char) && + (end_char >= location.end_char) && !comment.ignore? + while (node = body[body_index]) && + ( + node.is_a?(VoidStmt) || + node.location.start_char < location.start_char + ) body_index += 1 end - if body_index != 0 && body[body_index - 1].location.start_char < location.start_char && body[body_index - 1].location.end_char > location.start_char + if body_index != 0 && + body[body_index - 1].location.start_char < location.start_char && + body[body_index - 1].location.end_char > location.start_char # The previous node entirely encapsules the comment, so we don't # want to attach it here since it will get attached normally. This # is mostly in the case of hash and array literals. @@ -7996,7 +8173,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parts: parts, location: location } end end @@ -8034,14 +8211,14 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { left: left, right: right, location: location, comments: comments } end def format(q) q.group do q.format(left) - q.text(' \\') + q.text(" \\") q.indent do q.breakable(force: true) q.format(right) @@ -8079,7 +8256,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { variable: variable, location: location, comments: comments } end @@ -8119,7 +8296,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statements: statements, location: location, comments: comments } end @@ -8177,7 +8354,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parts: parts, quote: quote, location: location, comments: comments } end @@ -8240,7 +8417,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { arguments: arguments, location: location, comments: comments } end @@ -8293,7 +8470,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -8323,7 +8500,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -8357,7 +8534,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -8398,7 +8575,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { beginning: beginning, elements: elements, @@ -8454,7 +8631,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -8483,7 +8660,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -8513,7 +8690,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -8547,7 +8724,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { constant: constant, location: location, comments: comments } end @@ -8585,7 +8762,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { constant: constant, location: location, comments: comments } end @@ -8624,7 +8801,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -8664,7 +8841,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -8702,7 +8879,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -8738,7 +8915,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statement: statement, parentheses: parentheses, @@ -8749,7 +8926,9 @@ def deconstruct_keys(keys) def format(q) parent = q.parents.take(2)[1] - ternary = (parent.is_a?(If) || parent.is_a?(Unless)) && Ternaryable.call(q, parent) + ternary = + (parent.is_a?(If) || parent.is_a?(Unless)) && + Ternaryable.call(q, parent) q.text("not") @@ -8803,7 +8982,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { operator: operator, statement: statement, @@ -8823,6 +9002,9 @@ def format(q) # undef method # class Undef < Node + # Undef accepts a variable number of arguments that can be either DynaSymbol + # or SymbolLiteral objects. For SymbolLiteral objects we descend directly + # into the value in order to have it come out as bare words. class UndefArgumentFormatter # [DynaSymbol | SymbolLiteral] the symbol to undefine attr_reader :node @@ -8866,7 +9048,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { symbols: symbols, location: location, comments: comments } end @@ -8925,7 +9107,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { predicate: predicate, statements: statements, @@ -8971,7 +9153,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statement: statement, predicate: predicate, @@ -9010,13 +9192,15 @@ def format(q) end q.group do - q.if_break { format_break(q) }.if_flat do - Parentheses.flat(q) do - q.format(statements) - q.text(" #{keyword} ") - q.format(node.predicate) + q + .if_break { format_break(q) } + .if_flat do + Parentheses.flat(q) do + q.format(statements) + q.text(" #{keyword} ") + q.format(node.predicate) + end end - end end end @@ -9066,7 +9250,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { predicate: predicate, statements: statements, @@ -9122,7 +9306,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statement: statement, predicate: predicate, @@ -9188,7 +9372,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { left: left, right: right, location: location, comments: comments } end @@ -9231,7 +9415,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -9275,7 +9459,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -9316,7 +9500,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -9356,7 +9540,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -9391,7 +9575,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { location: location, comments: comments } end @@ -9442,7 +9626,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { arguments: arguments, statements: statements, @@ -9523,7 +9707,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { predicate: predicate, statements: statements, @@ -9579,7 +9763,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { statement: statement, predicate: predicate, @@ -9648,7 +9832,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parts: parts, location: location, comments: comments } end @@ -9688,7 +9872,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { beginning: beginning, elements: elements, @@ -9744,7 +9928,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location } end end @@ -9773,7 +9957,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parts: parts, location: location } end end @@ -9806,7 +9990,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { parts: parts, location: location, comments: comments } end @@ -9844,7 +10028,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { arguments: arguments, location: location, comments: comments } end @@ -9894,7 +10078,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end @@ -9930,7 +10114,7 @@ def child_nodes alias deconstruct child_nodes - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { value: value, location: location, comments: comments } end diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index f167aa83..75d3c322 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module SyntaxTree + # Parser is a subclass of the Ripper library that subscribes to the stream of + # tokens and nodes coming from the parser and builds up a syntax tree. class Parser < Ripper # A special parser error so that we can get nice syntax displays on the # error message when prettier prints out the results. @@ -40,9 +42,11 @@ def initialize(start, line) @start = start @indices = [] - line.each_char.with_index(start) do |char, index| - char.bytesize.times { @indices << index } - end + line + .each_char + .with_index(start) do |char, index| + char.bytesize.times { @indices << index } + end end # Technically it's possible for the column index to be a negative value if @@ -130,10 +134,10 @@ def initialize(source, *) last_index = 0 @source.lines.each do |line| - if line.size == line.bytesize - @line_counts << SingleByteString.new(last_index) + @line_counts << if line.size == line.bytesize + SingleByteString.new(last_index) else - @line_counts << MultiByteString.new(last_index, line) + MultiByteString.new(last_index, line) end last_index += line.size @@ -239,7 +243,7 @@ def find_colon2_before(const) # By finding the next non-space character, we can make sure that the bounds # of the statement list are correct. def find_next_statement_start(position) - remaining = source[position..-1] + remaining = source[position..] if remaining.sub(/\A +/, "")[0] == "#" return position + remaining.index("\n") @@ -264,7 +268,7 @@ def on_BEGIN(statements) start_char, start_char - line_counts[lbrace.location.start_line - 1].start, rbrace.location.start_char, - rbrace.location.start_column, + rbrace.location.start_column ) keyword = find_token(Kw, "BEGIN") @@ -281,7 +285,13 @@ def on_BEGIN(statements) def on_CHAR(value) CHAR.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -313,8 +323,14 @@ def on_END(statements) def on___end__(value) @__end__ = EndContent.new( - value: source[(char_pos + value.length)..-1], - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + value: source[(char_pos + value.length)..], + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -423,7 +439,8 @@ def on_args_add_block(arguments, block) # If there are any arguments and the operator we found from the list is # not after them, then we're going to return the arguments as-is because # we're looking at an & that occurs before the arguments are done. - if arguments.parts.any? && operator.location.start_char < arguments.location.end_char + if arguments.parts.any? && + operator.location.start_char < arguments.location.end_char return arguments end @@ -478,7 +495,11 @@ def on_args_forward # :call-seq: # on_args_new: () -> Args def on_args_new - Args.new(parts: [], location: Location.fixed(line: lineno, column: current_column, char: char_pos)) + Args.new( + parts: [], + location: + Location.fixed(line: lineno, column: current_column, char: char_pos) + ) end # :call-seq: @@ -529,7 +550,7 @@ def on_aryptn(constant, requireds, rest, posts) # If there's the optional then keyword, then we'll delete that and use it # as the end bounds of the location. - if token = find_token(Kw, "then", consume: false) + if (token = find_token(Kw, "then", consume: false)) tokens.delete(token) location = location.to(token.location) end @@ -538,10 +559,10 @@ def on_aryptn(constant, requireds, rest, posts) # here because it currently doesn't have anything to use for its precise # location. If we hit a comma, then we've gone too far. if rest.is_a?(VarField) && rest.value.nil? - tokens.rindex do |token| - case token + tokens.rindex do |rtoken| + case rtoken in Op[value: "*"] - rest = VarField.new(value: nil, location: token.location) + rest = VarField.new(value: nil, location: rtoken.location) break in Comma break @@ -561,7 +582,13 @@ def on_aryptn(constant, requireds, rest, posts) # :call-seq: # on_assign: ( - # (ARefField | ConstPathField | Field | TopConstField | VarField) target, + # ( + # ARefField | + # ConstPathField | + # Field | + # TopConstField | + # VarField + # ) target, # untyped value # ) -> Assign def on_assign(target, value) @@ -586,7 +613,10 @@ def on_assoc_new(key, value) def on_assoc_splat(value) operator = find_token(Op, "**") - AssocSplat.new(value: value, location: operator.location.to(value.location)) + AssocSplat.new( + value: value, + location: operator.location.to(value.location) + ) end # def on_assoclist_from_args(assocs) @@ -598,7 +628,13 @@ def on_assoc_splat(value) def on_backref(value) Backref.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -608,7 +644,13 @@ def on_backtick(value) node = Backtick.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -616,7 +658,9 @@ def on_backtick(value) end # :call-seq: - # on_bare_assoc_hash: (Array[AssocNew | AssocSplat] assocs) -> BareAssocHash + # on_bare_assoc_hash: ( + # Array[AssocNew | AssocSplat] assocs + # ) -> BareAssocHash def on_bare_assoc_hash(assocs) BareAssocHash.new( assocs: assocs, @@ -641,7 +685,7 @@ def on_begin(bodystmt) keyword = find_token(Kw, "begin") end_location = if bodystmt.rescue_clause || bodystmt.ensure_clause || - bodystmt.else_clause + bodystmt.else_clause bodystmt.location else find_token(Kw, "end").location @@ -660,7 +704,11 @@ def on_begin(bodystmt) end # :call-seq: - # on_binary: (untyped left, (Op | Symbol) operator, untyped right) -> Binary + # on_binary: ( + # untyped left, + # (Op | Symbol) operator, + # untyped right + # ) -> Binary def on_binary(left, operator, right) if operator.is_a?(Symbol) # Here, we're going to search backward for the token that's between the @@ -737,7 +785,8 @@ def on_bodystmt(statements, rescue_clause, else_clause, ensure_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, column: current_column) + location: + Location.fixed(line: lineno, char: char_pos, column: current_column) ) end @@ -764,7 +813,10 @@ def on_brace_block(block_var, statements) 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_line: [ + rbrace.location.end_line, + statements.location.end_line + ].max, end_char: rbrace.location.end_char, end_column: rbrace.location.end_column ) @@ -816,7 +868,7 @@ def on_call(receiver, operator, message) # :call-seq: # on_case: (untyped value, untyped consequent) -> Case | RAssign def on_case(value, consequent) - if keyword = find_token(Kw, "case", consume: false) + if (keyword = find_token(Kw, "case", consume: false)) tokens.delete(keyword) Case.new( @@ -870,7 +922,13 @@ def on_comma(value) node = Comma.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -915,7 +973,12 @@ def on_comment(value) value: value.chomp, inline: value.strip != lines[line - 1].strip, location: - Location.token(line: line, char: char_pos, column: current_column, size: value.size - 1) + Location.token( + line: line, + char: char_pos, + column: current_column, + size: value.size - 1 + ) ) @comments << comment @@ -927,7 +990,13 @@ def on_comment(value) def on_const(value) Const.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -962,7 +1031,13 @@ def on_const_ref(constant) def on_cvar(value) CVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -1047,7 +1122,10 @@ def on_defined(value) ending = find_token(RParen) end - Defined.new(value: value, location: beginning.location.to(ending.location)) + Defined.new( + value: value, + location: beginning.location.to(ending.location) + ) end # :call-seq: @@ -1268,7 +1346,8 @@ def on_embdoc_beg(value) @embdoc = EmbDoc.new( value: value, - location: Location.fixed(line: lineno, column: current_column, char: char_pos) + location: + Location.fixed(line: lineno, column: current_column, char: char_pos) ) end @@ -1302,7 +1381,13 @@ def on_embexpr_beg(value) node = EmbExprBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -1315,7 +1400,13 @@ def on_embexpr_end(value) node = EmbExprEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -1328,7 +1419,13 @@ def on_embvar(value) node = EmbVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -1395,7 +1492,13 @@ def on_field(parent, operator, name) def on_float(value) FloatLiteral.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -1413,8 +1516,7 @@ def on_fndptn(constant, left, values, right) # the location of the node. opening = find_token(LBracket, consume: false) || - find_token(LParen, consume: false) || - left + find_token(LParen, consume: false) || left # The closing is based on the opening, which is either the matched # punctuation or the right splat. @@ -1453,8 +1555,9 @@ def on_for(index, collection, statements) # Consume the do keyword if it exists so that it doesn't get confused for # some other block keyword = find_token(Kw, "do", consume: false) - if keyword && keyword.location.start_char > collection.location.end_char && - keyword.location.end_char < ending.location.start_char + if keyword && + keyword.location.start_char > collection.location.end_char && + keyword.location.end_char < ending.location.start_char tokens.delete(keyword) end @@ -1483,7 +1586,13 @@ def on_for(index, collection, statements) def on_gvar(value) GVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -1504,7 +1613,12 @@ def on_hash(assocs) # on_heredoc_beg: (String value) -> HeredocBeg def on_heredoc_beg(value) location = - Location.token(line: lineno, char: char_pos, column: current_column, 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. @@ -1545,7 +1659,7 @@ def on_heredoc_end(value) start_column: heredoc.location.start_column, end_line: lineno, end_char: char_pos, - end_column: current_column, + end_column: current_column ) ) end @@ -1563,24 +1677,32 @@ def on_hshptn(constant, keywords, keyword_rest) keyword_rest = VarField.new(value: nil, location: token.location) end + parts = [constant, *keywords&.flatten(1), keyword_rest].compact + + # If there's no constant, there may be braces, so we're going to look for + # those to get our bounds. + unless constant + lbrace = find_token(LBrace, consume: false) + rbrace = find_token(RBrace, consume: false) + + if lbrace && rbrace + parts = [lbrace, *parts, rbrace] + tokens.delete(lbrace) + tokens.delete(rbrace) + end + end + # Delete the optional then keyword - if token = find_token(Kw, "then", consume: false) + if (token = find_token(Kw, "then", consume: false)) + parts << token tokens.delete(token) end - parts = [constant, *keywords&.flatten(1), keyword_rest].compact - location = - if parts.any? - parts[0].location.to(parts[-1].location) - else - find_token(LBrace).location.to(find_token(RBrace).location) - end - HshPtn.new( constant: constant, keywords: keywords || [], keyword_rest: keyword_rest, - location: location + location: parts[0].location.to(parts[-1].location) ) end @@ -1589,7 +1711,13 @@ def on_hshptn(constant, keywords, keyword_rest) def on_ident(value) Ident.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -1654,7 +1782,13 @@ def on_if_mod(predicate, statement) def on_imaginary(value) Imaginary.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -1673,7 +1807,7 @@ def on_in(pattern, statements, consequent) ending = consequent || find_token(Kw, "end") statements_start = pattern - if token = find_token(Kw, "then", consume: false) + if (token = find_token(Kw, "then", consume: false)) tokens.delete(token) statements_start = token end @@ -1681,7 +1815,8 @@ def on_in(pattern, statements, consequent) start_char = find_next_statement_start(statements_start.location.end_char) statements.bind( start_char, - start_char - line_counts[statements_start.location.start_line - 1].start, + start_char - + line_counts[statements_start.location.start_line - 1].start, ending.location.start_char, ending.location.start_column ) @@ -1699,7 +1834,13 @@ def on_in(pattern, statements, consequent) def on_int(value) Int.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -1708,7 +1849,13 @@ def on_int(value) def on_ivar(value) IVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -1718,7 +1865,13 @@ def on_kw(value) node = Kw.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -1739,7 +1892,13 @@ def on_kwrest_param(name) def on_label(value) Label.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -1749,7 +1908,13 @@ def on_label_end(value) node = LabelEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -1763,11 +1928,13 @@ def on_label_end(value) # ) -> Lambda def on_lambda(params, statements) beginning = find_token(TLambda) - - if tokens.any? { |token| + braces = + tokens.any? do |token| token.is_a?(TLamBeg) && token.location.start_char > beginning.location.start_char - } + end + + if braces opening = find_token(TLamBeg) closing = find_token(RBrace) else @@ -1795,7 +1962,13 @@ def on_lbrace(value) node = LBrace.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -1808,7 +1981,13 @@ def on_lbracket(value) node = LBracket.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -1821,7 +2000,13 @@ def on_lparen(value) node = LParen.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -1920,7 +2105,11 @@ 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, column: current_column)) + MLHS.new( + parts: [], + location: + Location.fixed(line: lineno, char: char_pos, column: current_column) + ) end # :call-seq: @@ -1965,18 +2154,18 @@ 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, column: current_column)) + MRHS.new( + parts: [], + location: + Location.fixed(line: lineno, char: char_pos, column: current_column) + ) end # :call-seq: # on_mrhs_add: (MRHS mrhs, untyped part) -> MRHS def on_mrhs_add(mrhs, part) location = - if mrhs.parts.empty? - mrhs.location - else - mrhs.location.to(part.location) - end + (mrhs.parts.empty? ? mrhs.location : mrhs.location.to(part.location)) MRHS.new(parts: mrhs.parts << part, location: location) end @@ -2034,7 +2223,13 @@ def on_op(value) node = Op.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2043,7 +2238,13 @@ def on_op(value) # :call-seq: # on_opassign: ( - # (ARefField | ConstPathField | Field | TopConstField | VarField) target, + # ( + # ARefField | + # ConstPathField | + # Field | + # TopConstField | + # VarField + # ) target, # Op operator, # untyped value # ) -> OpAssign @@ -2118,14 +2319,15 @@ def on_paren(contents) lparen = find_token(LParen) rparen = find_token(RParen) - if contents && contents.is_a?(Params) + if 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: start_char, - start_column: start_char - line_counts[lparen.location.start_line - 1].start, + start_column: + start_char - line_counts[lparen.location.start_line - 1].start, end_line: location.end_line, end_char: rparen.location.start_char, end_column: rparen.location.start_column @@ -2166,7 +2368,13 @@ def on_parse_error(error, *) def on_period(value) Period.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -2298,7 +2506,13 @@ def on_qsymbols_beg(value) node = QSymbolsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2333,7 +2547,13 @@ def on_qwords_beg(value) node = QWordsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2345,7 +2565,11 @@ def on_qwords_beg(value) def on_qwords_new beginning = find_token(QWordsBeg) - QWords.new(beginning: beginning, elements: [], location: beginning.location) + QWords.new( + beginning: beginning, + elements: [], + location: beginning.location + ) end # :call-seq: @@ -2353,7 +2577,13 @@ def on_qwords_new def on_rational(value) RationalLiteral.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -2363,7 +2593,13 @@ def on_rbrace(value) node = RBrace.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2376,7 +2612,13 @@ def on_rbracket(value) node = RBracket.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2410,7 +2652,13 @@ def on_regexp_beg(value) node = RegexpBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2422,7 +2670,13 @@ def on_regexp_beg(value) def on_regexp_end(value) RegexpEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -2563,7 +2817,13 @@ def on_rparen(value) node = RParen.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2611,7 +2871,11 @@ def on_stmts_add(statements, statement) statements.location.to(statement.location) end - Statements.new(self, body: statements.body << statement, location: location) + Statements.new( + self, + body: statements.body << statement, + location: location + ) end # :call-seq: @@ -2620,7 +2884,8 @@ def on_stmts_new Statements.new( self, body: [], - location: Location.fixed(line: lineno, char: char_pos, column: current_column) + location: + Location.fixed(line: lineno, char: char_pos, column: current_column) ) end @@ -2654,7 +2919,8 @@ def on_string_concat(left, right) def on_string_content StringContent.new( parts: [], - location: Location.fixed(line: lineno, char: char_pos, column: current_column) + location: + Location.fixed(line: lineno, char: char_pos, column: current_column) ) end @@ -2703,7 +2969,7 @@ def on_string_embexpr(statements) def on_string_literal(string) heredoc = @heredocs[-1] - if heredoc && heredoc.ending + if heredoc&.ending heredoc = @heredocs.pop Heredoc.new( @@ -2756,7 +3022,13 @@ def on_symbeg(value) node = SymBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2810,7 +3082,13 @@ def on_symbols_beg(value) node = SymbolsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2835,7 +3113,13 @@ def on_tlambda(value) node = TLambda.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2848,7 +3132,13 @@ def on_tlambeg(value) node = TLamBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2883,7 +3173,13 @@ def on_tstring_beg(value) node = TStringBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -2895,7 +3191,13 @@ def on_tstring_beg(value) def on_tstring_content(value) TStringContent.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end @@ -2905,7 +3207,13 @@ def on_tstring_end(value) node = TStringEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -3014,7 +3322,7 @@ def on_until(predicate, statements) # some other block keyword = find_token(Kw, "do", consume: false) if keyword && keyword.location.start_char > predicate.location.end_char && - keyword.location.end_char < ending.location.start_char + keyword.location.end_char < ending.location.start_char tokens.delete(keyword) end @@ -3081,7 +3389,10 @@ def on_var_ref(value) if pin && pin.location.start_char == value.location.start_char - 1 tokens.delete(pin) - PinnedVarRef.new(value: value, location: pin.location.to(value.location)) + PinnedVarRef.new( + value: value, + location: pin.location.to(value.location) + ) else VarRef.new(value: value, location: value.location) end @@ -3096,7 +3407,10 @@ def on_vcall(ident) # :call-seq: # on_void_stmt: () -> VoidStmt def on_void_stmt - VoidStmt.new(location: Location.fixed(line: lineno, char: char_pos, column: current_column)) + VoidStmt.new( + location: + Location.fixed(line: lineno, char: char_pos, column: current_column) + ) end # :call-seq: @@ -3110,7 +3424,7 @@ def on_when(arguments, statements, consequent) ending = consequent || find_token(Kw, "end") statements_start = arguments - if token = find_token(Kw, "then", consume: false) + if (token = find_token(Kw, "then", consume: false)) tokens.delete(token) statements_start = token end @@ -3119,7 +3433,8 @@ def on_when(arguments, statements, consequent) statements.bind( start_char, - start_char - line_counts[statements_start.location.start_line - 1].start, + start_char - + line_counts[statements_start.location.start_line - 1].start, ending.location.start_char, ending.location.start_column ) @@ -3142,7 +3457,7 @@ def on_while(predicate, statements) # some other block keyword = find_token(Kw, "do", consume: false) if keyword && keyword.location.start_char > predicate.location.end_char && - keyword.location.end_char < ending.location.start_char + keyword.location.end_char < ending.location.start_char tokens.delete(keyword) end @@ -3188,7 +3503,11 @@ 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, column: current_column)) + Word.new( + parts: [], + location: + Location.fixed(line: lineno, char: char_pos, column: current_column) + ) end # :call-seq: @@ -3207,7 +3526,13 @@ def on_words_beg(value) node = WordsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) tokens << node @@ -3219,7 +3544,11 @@ def on_words_beg(value) def on_words_new beginning = find_token(WordsBeg) - Words.new(beginning: beginning, elements: [], location: beginning.location) + Words.new( + beginning: beginning, + elements: [], + location: beginning.location + ) end # def on_words_sep(value) diff --git a/lib/syntax_tree/prettyprint.rb b/lib/syntax_tree/prettyprint.rb index 0950ddfa..7fe64a56 100644 --- a/lib/syntax_tree/prettyprint.rb +++ b/lib/syntax_tree/prettyprint.rb @@ -375,7 +375,7 @@ class SingleLine # This argument is a noop. # * +newline+ - Argument position expected to be here for compatibility. # This argument is a noop. - def initialize(output, maxwidth = nil, newline = nil) + def initialize(output, _maxwidth = nil, _newline = nil) @output = Buffer.for(output) @target = @output @line_suffixes = Buffer::ArrayBuffer.new @@ -397,7 +397,7 @@ def flush # They are all noop arguments. def breakable( separator = " ", - width = separator.length, + _width = separator.length, indent: nil, force: nil ) @@ -410,7 +410,7 @@ def break_parent # Appends +separator+ to the output buffer. +width+ is a noop here for # compatibility. - def fill_breakable(separator = " ", width = separator.length) + def fill_breakable(separator = " ", _width = separator.length) target << separator end @@ -419,9 +419,9 @@ def trim target.trim! end - # ---------------------------------------------------------------------------- + # -------------------------------------------------------------------------- # Container node builders - # ---------------------------------------------------------------------------- + # -------------------------------------------------------------------------- # Opens a block for grouping objects to be pretty printed. # @@ -432,11 +432,11 @@ def trim # * +open_width+ - noop argument. Present for compatibility. # * +close_width+ - noop argument. Present for compatibility. def group( - indent = nil, + _indent = nil, open_object = "", close_object = "", - open_width = nil, - close_width = nil + _open_width = nil, + _close_width = nil ) target << open_object yield @@ -478,14 +478,14 @@ def line_suffix # Takes +indent+ arg, but does nothing with it. # # Yields to a block. - def nest(indent) + def nest(_indent) yield end # Add +object+ to the text to be output. # # +width+ argument is here for compatibility. It is a noop argument. - def text(object = "", width = nil) + def text(object = "", _width = nil) target << object end end @@ -546,17 +546,17 @@ def indent(part = IndentPart) last_spaces = 0 end - next_queue.each do |part| - case part + next_queue.each do |next_part| + case next_part when IndentPart flush_spaces.call add_spaces.call(2) when StringAlignPart flush_spaces.call - next_value += part.n - next_length += part.n.length + next_value += next_part.n + next_length += next_part.n.length when NumberAlignPart - last_spaces += part.n + last_spaces += next_part.n end end @@ -623,9 +623,9 @@ def self.format( # def self.singleline_format( output = "".dup, - maxwidth = nil, - newline = nil, - genspace = nil + _maxwidth = nil, + _newline = nil, + _genspace = nil ) q = SingleLine.new(output) yield q @@ -778,16 +778,19 @@ def flush position -= buffer.trim! when Group if mode == MODE_FLAT && !should_remeasure - commands << - [indent, doc.break? ? MODE_BREAK : MODE_FLAT, doc.contents] + commands << [ + indent, + doc.break? ? MODE_BREAK : MODE_FLAT, + doc.contents + ] else should_remeasure = false next_cmd = [indent, MODE_FLAT, doc.contents] - - if !doc.break? && fits?(next_cmd, commands, maxwidth - position) - commands << next_cmd + commands << if !doc.break? && + fits?(next_cmd, commands, maxwidth - position) + next_cmd else - commands << [indent, MODE_BREAK, doc.contents] + [indent, MODE_BREAK, doc.contents] end end when IfBreak @@ -1060,7 +1063,7 @@ def nest(indent) def text(object = "", width = object.length) doc = target.last - unless Text === doc + unless doc.is_a?(Text) doc = Text.new target << doc end diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index 63227d03..57794ddb 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true module SyntaxTree + # Visitor is a parent class that provides the ability to walk down the tree + # and handle a subset of nodes. By defining your own subclass, you can + # explicitly handle a node type by defining a visit_* method. class Visitor # This is raised when you use the Visitor.visit_method method and it fails. # It is correctable to through DidYouMean. @@ -24,7 +27,9 @@ def initialize(error) def corrections @corrections ||= - DidYouMean::SpellChecker.new(dictionary: Visitor.visit_methods).correct(visit_method) + DidYouMean::SpellChecker.new( + dictionary: Visitor.visit_methods + ).correct(visit_method) end DidYouMean.correct_error(VisitMethodError, self) diff --git a/lib/syntax_tree/visitor/field_visitor.rb b/lib/syntax_tree/visitor/field_visitor.rb index 1a24f40d..06e8945f 100644 --- a/lib/syntax_tree/visitor/field_visitor.rb +++ b/lib/syntax_tree/visitor/field_visitor.rb @@ -220,9 +220,7 @@ def visit_class(node) end def visit_comma(node) - node(node, "comma") do - field("value", node) - end + node(node, "comma") { field("value", node) } end def visit_command(node) @@ -244,9 +242,7 @@ def visit_command_call(node) end def visit_comment(node) - node(node, "comment") do - field("value", node.value) - end + node(node, "comment") { field("value", node.value) } end def visit_const(node) @@ -376,27 +372,19 @@ def visit_elsif(node) end def visit_embdoc(node) - node(node, "embdoc") do - field("value", node.value) - end + node(node, "embdoc") { field("value", node.value) } end def visit_embexpr_beg(node) - node(node, "embexpr_beg") do - field("value", node.value) - end + node(node, "embexpr_beg") { field("value", node.value) } end def visit_embexpr_end(node) - node(node, "embexpr_end") do - field("value", node.value) - end + node(node, "embexpr_end") { field("value", node.value) } end def visit_embvar(node) - node(node, "embvar") do - field("value", node.value) - end + node(node, "embvar") { field("value", node.value) } end def visit_ensure(node) @@ -548,9 +536,7 @@ def visit_label(node) end def visit_label_end(node) - node(node, "label_end") do - field("value", node.value) - end + node(node, "label_end") { field("value", node.value) } end def visit_lambda(node) @@ -698,9 +684,7 @@ def visit_qsymbols(node) end def visit_qsymbols_beg(node) - node(node, "qsymbols_beg") do - field("value", node.value) - end + node(node, "qsymbols_beg") { field("value", node.value) } end def visit_qwords(node) @@ -711,9 +695,7 @@ def visit_qwords(node) end def visit_qwords_beg(node) - node(node, "qwords_beg") do - field("value", node.value) - end + node(node, "qwords_beg") { field("value", node.value) } end def visit_rassign(node) @@ -730,15 +712,11 @@ def visit_rational(node) end def visit_rbrace(node) - node(node, "rbrace") do - field("value", node.value) - end + node(node, "rbrace") { field("value", node.value) } end def visit_rbracket(node) - node(node, "rbracket") do - field("value", node.value) - end + node(node, "rbracket") { field("value", node.value) } end def visit_redo(node) @@ -746,21 +724,15 @@ def visit_redo(node) end def visit_regexp_beg(node) - node(node, "regexp_beg") do - field("value", node.value) - end + node(node, "regexp_beg") { field("value", node.value) } end def visit_regexp_content(node) - node(node, "regexp_content") do - list("parts", node.parts) - end + node(node, "regexp_content") { list("parts", node.parts) } end def visit_regexp_end(node) - node(node, "regexp_end") do - field("value", node.value) - end + node(node, "regexp_end") { field("value", node.value) } end def visit_regexp_literal(node) @@ -818,9 +790,7 @@ def visit_return0(node) end def visit_rparen(node) - node(node, "rparen") do - field("value", node.value) - end + node(node, "rparen") { field("value", node.value) } end def visit_sclass(node) @@ -847,9 +817,7 @@ def visit_string_concat(node) end def visit_string_content(node) - node(node, "string_content") do - list("parts", node.parts) - end + node(node, "string_content") { list("parts", node.parts) } end def visit_string_dvar(node) @@ -881,15 +849,11 @@ def visit_super(node) end def visit_symbeg(node) - node(node, "symbeg") do - field("value", node.value) - end + node(node, "symbeg") { field("value", node.value) } end def visit_symbol_content(node) - node(node, "symbol_content") do - field("value", node.value) - end + node(node, "symbol_content") { field("value", node.value) } end def visit_symbol_literal(node) @@ -907,21 +871,15 @@ def visit_symbols(node) end def visit_symbols_beg(node) - node(node, "symbols_beg") do - field("value", node.value) - end + node(node, "symbols_beg") { field("value", node.value) } end def visit_tlambda(node) - node(node, "tlambda") do - field("value", node.value) - end + node(node, "tlambda") { field("value", node.value) } end def visit_tlambeg(node) - node(node, "tlambeg") do - field("value", node.value) - end + node(node, "tlambeg") { field("value", node.value) } end def visit_top_const_field(node) @@ -939,9 +897,7 @@ def visit_top_const_ref(node) end def visit_tstring_beg(node) - node(node, "tstring_beg") do - field("value", node.value) - end + node(node, "tstring_beg") { field("value", node.value) } end def visit_tstring_content(node) @@ -949,9 +905,7 @@ def visit_tstring_content(node) end def visit_tstring_end(node) - node(node, "tstring_end") do - field("value", node.value) - end + node(node, "tstring_end") { field("value", node.value) } end def visit_unary(node) @@ -1032,9 +986,7 @@ def visit_vcall(node) end def visit_void_stmt(node) - node(node, "void_stmt") do - comments(node) - end + node(node, "void_stmt") { comments(node) } end def visit_when(node) @@ -1077,15 +1029,11 @@ def visit_words(node) end def visit_words_beg(node) - node(node, "words_beg") do - field("value", node.value) - end + node(node, "words_beg") { field("value", node.value) } end def visit_xstring(node) - node(node, "xstring") do - list("parts", node.parts) - end + node(node, "xstring") { list("parts", node.parts) } end def visit_xstring_literal(node) diff --git a/lib/syntax_tree/visitor/json_visitor.rb b/lib/syntax_tree/visitor/json_visitor.rb index fb98a999..b516980c 100644 --- a/lib/syntax_tree/visitor/json_visitor.rb +++ b/lib/syntax_tree/visitor/json_visitor.rb @@ -1,4 +1,4 @@ - # frozen_string_literal: true +# frozen_string_literal: true module SyntaxTree class Visitor diff --git a/lib/syntax_tree/visitor/match_visitor.rb b/lib/syntax_tree/visitor/match_visitor.rb index ab88ae58..53caf4c5 100644 --- a/lib/syntax_tree/visitor/match_visitor.rb +++ b/lib/syntax_tree/visitor/match_visitor.rb @@ -56,7 +56,7 @@ def list(name, values) end end - def node(node, type) + def node(node, _type) items = [] q.with_target(items) { yield } diff --git a/lib/syntax_tree/visitor/pretty_print_visitor.rb b/lib/syntax_tree/visitor/pretty_print_visitor.rb index c74a4cc9..a45eec44 100644 --- a/lib/syntax_tree/visitor/pretty_print_visitor.rb +++ b/lib/syntax_tree/visitor/pretty_print_visitor.rb @@ -1,4 +1,4 @@ - # frozen_string_literal: true +# frozen_string_literal: true module SyntaxTree class Visitor @@ -47,7 +47,7 @@ def list(_name, values) end end - def node(node, type) + def node(_node, type) q.group(2, "(", ")") do q.text(type) yield diff --git a/syntax_tree.gemspec b/syntax_tree.gemspec index d17296f6..06a7ed78 100644 --- a/syntax_tree.gemspec +++ b/syntax_tree.gemspec @@ -1,30 +1,32 @@ # frozen_string_literal: true -require_relative 'lib/syntax_tree/version' +require_relative "lib/syntax_tree/version" Gem::Specification.new do |spec| - spec.name = 'syntax_tree' - spec.version = SyntaxTree::VERSION - spec.authors = ['Kevin Newton'] - spec.email = ['kddnewton@gmail.com'] + spec.name = "syntax_tree" + spec.version = SyntaxTree::VERSION + spec.authors = ["Kevin Newton"] + spec.email = ["kddnewton@gmail.com"] - spec.summary = 'A parser based on ripper' - spec.homepage = 'https://github.com/kddnewton/syntax_tree' - spec.license = 'MIT' - spec.metadata = { 'rubygems_mfa_required' => 'true' } + spec.summary = "A parser based on ripper" + spec.homepage = "https://github.com/kddnewton/syntax_tree" + spec.license = "MIT" + spec.metadata = { "rubygems_mfa_required" => "true" } - spec.files = Dir.chdir(__dir__) do - `git ls-files -z`.split("\x0").reject do |f| - f.match(%r{^(test|spec|features)/}) + spec.files = + Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0") + .reject { |f| f.match(%r{^(test|spec|features)/}) } end - end - spec.bindir = 'exe' - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.required_ruby_version = ">= 2.7.3" + + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = %w[lib] - spec.add_development_dependency 'bundler' - spec.add_development_dependency 'minitest' - spec.add_development_dependency 'rake' - spec.add_development_dependency 'simplecov' + spec.add_development_dependency "bundler" + spec.add_development_dependency "minitest" + spec.add_development_dependency "rake" + spec.add_development_dependency "simplecov" end diff --git a/test/fixtures/hshptn.rb b/test/fixtures/hshptn.rb index b2474ea9..2935f9c1 100644 --- a/test/fixtures/hshptn.rb +++ b/test/fixtures/hshptn.rb @@ -14,18 +14,28 @@ case foo in bar:, baz: end +- +case foo +in { bar:, baz: } +end % case foo in bar: bar, baz: baz end +- +case foo +in { bar: bar, baz: baz } +end % case foo in **bar end % case foo -in foo:, # comment1 - bar: # comment2 +in { + foo:, # comment1 + bar: # comment2 + } baz end % diff --git a/test/formatting_test.rb b/test/formatting_test.rb index 5f51d471..74852cc2 100644 --- a/test/formatting_test.rb +++ b/test/formatting_test.rb @@ -12,7 +12,10 @@ class FormattingTest < Minitest::Test def test_format_class_level source = "1+1" - assert_equal("1 + 1\n", SyntaxTree::Formatter.format(source, SyntaxTree.parse(source))) + assert_equal( + "1 + 1\n", + SyntaxTree::Formatter.format(source, SyntaxTree.parse(source)) + ) end end end diff --git a/test/idempotency_test.rb b/test/idempotency_test.rb index bb12bdbb..1f560db2 100644 --- a/test/idempotency_test.rb +++ b/test/idempotency_test.rb @@ -10,7 +10,11 @@ class IdempotencyTest < Minitest::Test source = SyntaxTree.read(filepath) formatted = SyntaxTree.format(source) - assert_equal(formatted, SyntaxTree.format(formatted), "expected #{filepath} to be formatted idempotently") + assert_equal( + formatted, + SyntaxTree.format(formatted), + "expected #{filepath} to be formatted idempotently" + ) end end end diff --git a/test/parser_test.rb b/test/parser_test.rb index e5861398..8aadbfc2 100644 --- a/test/parser_test.rb +++ b/test/parser_test.rb @@ -9,7 +9,8 @@ def test_parses_ripper_methods 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 } + events -= + Parser.private_instance_methods(false).grep(/^on_(\w+)/) { $1.to_sym } # Next, subtract the list of events that we purposefully skipped. events -= %i[ diff --git a/test/test_helper.rb b/test/test_helper.rb index 09c4dd0a..1d55765b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -22,12 +22,9 @@ module Fixtures fndptn rassign rassign_rocket - ] + ].freeze - FIXTURES_3_1_0 = %w[ - pinned_begin - var_field_rassign - ] + FIXTURES_3_1_0 = %w[pinned_begin var_field_rassign].freeze Fixture = Struct.new(:name, :source, :formatted, keyword_init: true) @@ -50,20 +47,30 @@ def self.each_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") + 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 + # 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?(">=") && + (ruby_version < Gem::Version.new(comment.split[1])) + next + end - name = :"#{fixture}_#{index}" - yield Fixture.new(name: name, source: source, formatted: formatted || source) - 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 index 00cedda4..89a952f2 100644 --- a/test/visitor_test.rb +++ b/test/visitor_test.rb @@ -12,7 +12,9 @@ def test_visit_all_nodes program.statements.body.last.bodystmt.statements.body.each do |node| case node - in SyntaxTree::ClassDeclaration[superclass: { value: { value: "Node" } }] + in SyntaxTree::ClassDeclaration[ + superclass: { value: { value: "Node" } } + ] # this is a class we want to look at else next @@ -29,7 +31,11 @@ def test_visit_all_nodes end case accept - in { bodystmt: { statements: { body: [SyntaxTree::Call[message: { value: visit_method }]] } } } + in bodystmt: { + statements: { + body: [SyntaxTree::Call[message: { value: visit_method }]] + } + } assert_respond_to(visitor, visit_method) end end @@ -51,7 +57,7 @@ def baz; end visitor = DummyVisitor.new visitor.visit(parsed_tree) - assert_equal(["Foo", "foo", "Bar", "bar", "baz"], visitor.visited_nodes) + assert_equal(%w[Foo foo Bar bar baz], visitor.visited_nodes) end class DummyVisitor < SyntaxTree::Visitor