pax_global_header00006660000000000000000000000064137336510400014514gustar00rootroot0000000000000052 comment=3e585ad838c85ee34407db9f9166e645f6a87101 rubocop-ast-0.6.0/000077500000000000000000000000001373365104000137555ustar00rootroot00000000000000rubocop-ast-0.6.0/.github/000077500000000000000000000000001373365104000153155ustar00rootroot00000000000000rubocop-ast-0.6.0/.github/workflows/000077500000000000000000000000001373365104000173525ustar00rootroot00000000000000rubocop-ast-0.6.0/.github/workflows/rubocop.yml000066400000000000000000000103041373365104000215440ustar00rootroot00000000000000# CAUTION: There's probably a way to refactor this nicely. PR welcome. # NOTE: Reflect changes in .mergify.yml # NOTE: When changing minimal version of Ruby or Rubocop, change all of them name: CI on: [push, pull_request] jobs: ast_specs: name: >- ${{ matrix.title || 'Specs' }} | RuboCop: ${{ matrix.rubocop }} | ${{ matrix.ruby }} (${{ matrix.os }}) runs-on: ${{ matrix.os }}-latest env: # See https://github.com/tmm1/test-queue#environment-variables TEST_QUEUE_WORKERS: 2 strategy: fail-fast: false matrix: os: [ ubuntu ] ruby: [ 2.4, 2.5, 2.6, 2.7, head ] rubocop: [ master ] coverage: [ null ] modern: [ null ] internal_investigation: [ null ] title: [ null ] include: - { os: windows, rubocop: master, ruby: mingw } - { rubocop: '0.92.0', ruby: 2.4, os: ubuntu } - { rubocop: '0.92.0', ruby: head, os: ubuntu } - { rubocop: '0.92.0', ruby: 2.4, os: ubuntu, coverage: true, title: 'Coverage' } - { rubocop: master, ruby: 2.7, os: ubuntu, modern: true, title: 'Specs "modern"' } - { rubocop: master, ruby: 2.7, os: ubuntu, internal_investigation: true, modern: true, title: 'Coding Style' } steps: - name: windows misc if: matrix.os == 'windows' run: | # set TMPDIR, git core.autocrlf echo "::set-env name=TMPDIR::$env:RUNNER_TEMP" git config --system core.autocrlf false - name: checkout uses: actions/checkout@v2 - name: set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - name: install dependencies run: bundle install --jobs 3 --retry 3 - name: install rubocop packages for internal investigation if: matrix.os != 'windows' env: RUBOCOP_VERSION: ${{ matrix.rubocop }} run: bundle install --gemfile Gemfile.ci - name: install rubocop from source for internal investigation if: "matrix.os != 'windows' && matrix.rubocop == 'master'" run: | git clone https://github.com/rubocop-hq/rubocop.git ../rubocop chmod +x ../rubocop/exe/rubocop cd ../rubocop && bundle install --jobs 3 --retry 3 - name: code coverage if: matrix.coverage uses: paambaati/codeclimate-action@v2.6.0 env: CC_TEST_REPORTER_ID: '758a8228862932dc8afa9144c4a5bc5dfe29c2f7dde1b7734175bad49ee310e7' COVERAGE: 'true' with: coverageCommand: bundle exec rake spec debug: true - name: set modernize mode if: matrix.modern == true run: echo '::set-env name=MODERNIZE::true' - name: spec if: "matrix.coverage != true && matrix.internal_investigation != true" run: bundle exec rake spec - name: internal investigation if: matrix.internal_investigation run: bundle exec rake internal_investigation rubocop_specs: name: >- Main Gem Specs | RuboCop: ${{ matrix.rubocop }} | ${{ matrix.ruby }} (${{ matrix.os }}) runs-on: ${{ matrix.os }}-latest env: # See https://github.com/tmm1/test-queue#environment-variables TEST_QUEUE_WORKERS: 2 strategy: fail-fast: false matrix: os: [ ubuntu ] ruby: [ 2.4, 2.7 ] rubocop: [ '0.92.0', master ] steps: - name: checkout uses: actions/checkout@v2 - name: set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - name: install dependencies run: bundle install --jobs 3 --retry 3 - name: clone rubocop from source for full specs -- master if: matrix.rubocop == 'master' run: git clone --branch ${{ matrix.rubocop }} https://github.com/rubocop-hq/rubocop.git ../rubocop - name: install rubocop from source for full specs -- branch if: matrix.rubocop != 'master' run: git clone --branch v${{ matrix.rubocop }} https://github.com/rubocop-hq/rubocop.git ../rubocop - name: install rubocop dependencies run: cd ../rubocop && bundle install --jobs 3 --retry 3 - name: spec run: cd ../rubocop && bundle exec rake spec rubocop-ast-0.6.0/.gitignore000066400000000000000000000016771373365104000157600ustar00rootroot00000000000000# rcov generated coverage coverage.data # rdoc generated rdoc # yard generated doc .yardoc # bundler .bundle Gemfile.lock Gemfile.local # rbenv .ruby-version # rspec /spec/examples.txt /.test_queue_stats # jeweler generated pkg # etags TAGS # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: # # * Create a file at ~/.gitignore # * Include files you want ignored # * Run: git config --global core.excludesfile ~/.gitignore # # After doing this, these files will be ignored in all your git projects, # saving you from having to 'pollute' every project you touch with them # # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) # # For MacOS: # #.DS_Store # For TextMate #*.tmproj #tmtags # For emacs: #*~ #\#* #.\#* # For vim: *.swp # For redcar: #.redcar # For rubinius: #*.rbc # For byebug .byebug_history rubocop-ast-0.6.0/.mergify.yml000066400000000000000000000026311373365104000162220ustar00rootroot00000000000000pull_request_rules: - name: automatic merge for master when reviewed and CI passes actions: merge: method: rebase conditions: - base=master - label=auto-merge - "#review-requested=0" - "#changes-requested-reviews-by=0" - 'status-success=Specs | RuboCop: master | 2.4 (ubuntu)' - 'status-success=Specs | RuboCop: master | 2.5 (ubuntu)' - 'status-success=Specs | RuboCop: master | 2.6 (ubuntu)' - 'status-success=Specs | RuboCop: master | 2.7 (ubuntu)' - 'status-success=Specs | RuboCop: master | head (ubuntu)' - 'status-success=Specs | RuboCop: master | mingw (windows)' - 'status-success=Specs | RuboCop: 0.89.0 | 2.4 (ubuntu)' - 'status-success=Specs | RuboCop: 0.89.0 | head (ubuntu)' - 'status-success=Coverage | RuboCop: 0.89.0 | 2.4 (ubuntu)' - 'status-success=Specs "modern" | RuboCop: master | 2.7 (ubuntu)' - 'status-success=Coding Style | RuboCop: master | 2.7 (ubuntu)' - 'status-success=Main Gem Specs | RuboCop: 0.89.0 | 2.4 (ubuntu)' - 'status-success=Main Gem Specs | RuboCop: master | 2.4 (ubuntu)' - 'status-success=Main Gem Specs | RuboCop: 0.89.0 | 2.7 (ubuntu)' - 'status-success=Main Gem Specs | RuboCop: master | 2.7 (ubuntu)' - name: delete head branch after auto-merge conditions: - merged - label=auto-merge actions: delete_head_branch: {} rubocop-ast-0.6.0/.rspec000066400000000000000000000000361373365104000150710ustar00rootroot00000000000000--color --require spec_helper rubocop-ast-0.6.0/.rubocop.yml000066400000000000000000000034551373365104000162360ustar00rootroot00000000000000# This is the configuration used to check the rubocop source code. inherit_from: .rubocop_todo.yml require: - rubocop/cop/internal_affairs - rubocop-performance - rubocop-rspec AllCops: NewCops: enable Exclude: - 'vendor/**/*' - 'spec/fixtures/**/*' - 'tmp/**/*' - '.git/**/*' TargetRubyVersion: 2.4 Naming/PredicateName: # Method define macros for dynamically generated method. MethodDefinitionMacros: - define_method - define_singleton_method - def_node_matcher - def_node_search Style/FormatStringToken: # Because we parse a lot of source codes from strings. Percent arrays # look like unannotated format string tokens to this cop. Exclude: - spec/**/* Layout/EndOfLine: EnforcedStyle: lf Layout/ClassStructure: Enabled: true Categories: module_inclusion: - include - prepend - extend ExpectedOrder: - module_inclusion - constants - public_class_methods - initializer - instance_methods - protected_methods - private_methods # Trailing white space is meaningful in code examples Layout/TrailingWhitespace: AllowInHeredoc: true Lint/AmbiguousBlockAssociation: Exclude: - 'spec/**/*.rb' Layout/HashAlignment: EnforcedHashRocketStyle: - key - table EnforcedColonStyle: - key - table Lint/InterpolationCheck: Exclude: - 'spec/**/*.rb' Lint/UselessAccessModifier: MethodCreatingMethods: - 'def_matcher' - 'def_node_matcher' Lint/BooleanSymbol: Enabled: false Metrics/BlockLength: Exclude: - 'Rakefile' - '**/*.rake' - 'spec/**/*.rb' - '**/*.gemspec' Metrics/ModuleLength: Exclude: - 'spec/**/*.rb' RSpec/PredicateMatcher: EnforcedStyle: explicit RSpec/MessageSpies: EnforcedStyle: receive RSpec/NestedGroups: Max: 7 rubocop-ast-0.6.0/.rubocop_todo.yml000066400000000000000000000054071373365104000172620ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config` # on 2020-05-26 21:57:54 -0400 using RuboCop version 0.83.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 1 # Configuration parameters: Include. # Include: **/*.gemspec Gemspec/RequiredRubyVersion: Exclude: - 'rubocop-ast.gemspec' # Offense count: 2 InternalAffairs/NodeDestructuring: Exclude: - 'spec/rubocop/ast/node_pattern_spec.rb' - 'spec/rubocop/ast/resbody_node_spec.rb' # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: Max: 138 # Offense count: 2 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 13 # Offense count: 1 # Configuration parameters: CountComments. Metrics/ModuleLength: Max: 101 # Offense count: 1 # Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms. # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS Naming/FileName: Exclude: - 'lib/rubocop-ast.rb' # Offense count: 1 RSpec/BeforeAfterAll: Exclude: - 'spec/spec_helper.rb' - 'spec/rails_helper.rb' - 'spec/support/**/*.rb' - 'spec/rubocop/ast/node_spec.rb' # Offense count: 180 # Configuration parameters: Prefixes. # Prefixes: when, with, without RSpec/ContextWording: Exclude: - 'spec/rubocop/ast/array_node_spec.rb' - 'spec/rubocop/ast/block_node_spec.rb' - 'spec/rubocop/ast/float_node_spec.rb' - 'spec/rubocop/ast/hash_node_spec.rb' - 'spec/rubocop/ast/int_node_spec.rb' - 'spec/rubocop/ast/node_pattern_spec.rb' - 'spec/rubocop/ast/node_spec.rb' - 'spec/rubocop/ast/processed_source_spec.rb' - 'spec/rubocop/ast/resbody_node_spec.rb' - 'spec/rubocop/ast/token_spec.rb' - 'spec/spec_helper.rb' # Offense count: 6 # Configuration parameters: Max. RSpec/ExampleLength: Exclude: - 'spec/rubocop/ast/node_pattern_spec.rb' - 'spec/rubocop/ast/processed_source_spec.rb' - 'spec/rubocop/ast/send_node_spec.rb' # Offense count: 6 RSpec/LeakyConstantDeclaration: Exclude: - 'spec/rubocop/ast/node_pattern_spec.rb' - 'spec/rubocop/ast/node_spec.rb' # Offense count: 50 RSpec/MultipleExpectations: Max: 5 # Offense count: 27 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Layout/LineLength: Max: 102 rubocop-ast-0.6.0/CHANGELOG.md000066400000000000000000000173741373365104000156020ustar00rootroot00000000000000# Change log ## master (unreleased) ## 0.6.0 (2020-09-26) ### New features * [#124](https://github.com/rubocop-hq/rubocop-ast/pull/124): Add `RegexpNode#options`. ([@owst][]) ## 0.5.1 (2020-09-25) ### Bug fixes * [#120](https://github.com/rubocop-hq/rubocop-ast/pull/120): **(Potentially breaking)** Fix false positives and negatives for `SendNode#macro?`. This impacts `{non_}bare_access_modifier?` and `special_access_modifier?`. ([@marcandre][]) ## 0.5.0 (2020-09-24) ### New features * [#122](https://github.com/rubocop-hq/rubocop-ast/pull/122): Add `Node#parent?` and `Node#root?`. ([@marcandre][]) ### Changes * [#121](https://github.com/rubocop-hq/rubocop-ast/pull/121): Update from `Parser::Ruby28` to `Parser::Ruby30` for Ruby 3.0 parser (experimental). ([@koic][]) ## 0.4.2 (2020-09-18) ### Bug fixes * [#116](https://github.com/rubocop-hq/rubocop-ast/pull/116): Fix issues with tokens being sometimes misordered. ([@fatkodima][]) ## 0.4.1 (2020-09-16) ### Bug fixes * [#115](https://github.com/rubocop-hq/rubocop-ast/pull/115): Fix `ConstNode#absolute?` when the constant is not namespaced. ([@dvandersluis][]) ## 0.4.0 (2020-09-11) ### New features * [#92](https://github.com/rubocop-hq/rubocop-ast/pull/92): Add `ProcessedSource#tokens_within`, `ProcessedSource#first_token_of` and `ProcessedSource#last_token_of`. ([@fatkodima][]) * [#88](https://github.com/rubocop-hq/rubocop-ast/pull/88): Add `RescueNode`. Add `ResbodyNode#exceptions` and `ResbodyNode#branch_index`. ([@fatkodima][]) * [#89](https://github.com/rubocop-hq/rubocop-ast/pull/89): Support right hand assignment for Ruby 2.8 (3.0) parser. ([@koic][]) * [#93](https://github.com/rubocop-hq/rubocop-ast/pull/93): Add `Node#{left|right}_sibling{s}` ([@marcandre][]) * [#99](https://github.com/rubocop-hq/rubocop-ast/pull/99): Add `ConstNode` and some helper methods. ([@marcandre][]) ### Changes * [#94](https://github.com/rubocop-hq/rubocop-ast/pull/94): In Ruby 2.4, `Set#===` is harmonized with Ruby 2.5+ to call `include?`. ([@marcandre][]) * [#91](https://github.com/rubocop-hq/rubocop-ast/pull/91): **(Potentially breaking)** `Node#arguments` always returns a frozen array ([@marcandre][]) ## 0.3.0 (2020-08-01) ### New features * [#70](https://github.com/rubocop-hq/rubocop-ast/pull/70): Add `NextNode` ([@marcandre][]) * [#85](https://github.com/rubocop-hq/rubocop-ast/pull/85): Add `IntNode#value` and `FloatNode#value`. ([@fatkodima][]) * [#82](https://github.com/rubocop-hq/rubocop-ast/pull/82): `NodePattern`: Allow comments ([@marcandre][]) * [#83](https://github.com/rubocop-hq/rubocop-ast/pull/83): Add `ProcessedSource#comment_at_line` ([@marcandre][]) * [#83](https://github.com/rubocop-hq/rubocop-ast/pull/83): Add `ProcessedSource#each_comment_in_lines` ([@marcandre][]) * [#84](https://github.com/rubocop-hq/rubocop-ast/pull/84): Add `Source::Range#line_span` ([@marcandre][]) * [#87](https://github.com/rubocop-hq/rubocop-ast/pull/87): Add `CaseNode#branches` ([@marcandre][]) ### Bug fixes * [#70](https://github.com/rubocop-hq/rubocop-ast/pull/70): Fix arguments processing for `BreakNode` ([@marcandre][]) * [#70](https://github.com/rubocop-hq/rubocop-ast/pull/70): **(Potentially breaking)** `BreakNode` and `ReturnNode` no longer include `MethodDispatchNode`. These methods were severely broken ([@marcandre][]) ### Changes * [#44](https://github.com/rubocop-hq/rubocop-ast/issue/44): **(Breaking)** Use `parser` flag `self.emit_forward_arg = true` by default. ([@marcandre][]) * [#86](https://github.com/rubocop-hq/rubocop-ast/pull/86): `PairNode#delimiter` and `inverse_delimiter` now accept their argument as a named argument. ([@marcandre][]) * [#87](https://github.com/rubocop-hq/rubocop-ast/pull/87): **(Potentially breaking)** Have `IfNode#branches` return a `nil` value if source has `else; end` ([@marcandre][]) * [#72](https://github.com/rubocop-hq/rubocop-ast/pull/72): **(Potentially breaking)** `SuperNode/DefinedNode/YieldNode#arguments` now return a frozen array. ([@marcandre][]) ## 0.2.0 (2020-07-19) ### New features * [#50](https://github.com/rubocop-hq/rubocop-ast/pull/50): Support find pattern matching for Ruby 2.8 (3.0) parser. ([@koic][]) * [#55](https://github.com/rubocop-hq/rubocop-ast/pull/55): Add `ProcessedSource#line_with_comment?`. ([@marcandre][]) * [#63](https://github.com/rubocop-hq/rubocop-ast/pull/63): NodePattern now supports patterns as arguments to predicate and functions. ([@marcandre][]) * [#64](https://github.com/rubocop-hq/rubocop-ast/pull/64): Add `Node#global_const?`. ([@marcandre][]) * [#28](https://github.com/rubocop-hq/rubocop-ast/issues/28): Add `struct_constructor?`, `class_definition?` and `module_definition?` matchers. ([@tejasbubane][]) ### Bug fixes * [#55](https://github.com/rubocop-hq/rubocop-ast/pull/55): Fix `ProcessedSource#commented?` for multi-line ranges. Renamed `contains_comment?` ([@marcandre][]) * [#69](https://github.com/rubocop-hq/rubocop-ast/pull/69): **(Potentially breaking)** `RetryNode` has many errors. It is now a `Node`. ([@marcandre][]) ## 0.1.0 (2020-06-26) ### New features * [#36](https://github.com/rubocop-hq/rubocop-ast/pull/36): Add `post_condition_loop?` and `loop_keyword?` for `Node`. ([@fatkodima][]) * [#38](https://github.com/rubocop-hq/rubocop-ast/pull/38): Add helpers allowing to check whether the method is a nonmutating operator method or a nonmutating method of several core classes. ([@fatkodima][]) * [#37](https://github.com/rubocop-hq/rubocop-ast/pull/37): Add `enumerable_method?` for `MethodIdentifierPredicates`. ([@fatkodima][]) * [#4](https://github.com/rubocop-hq/rubocop-ast/issues/4): Add `interpolation?` for `RegexpNode`. ([@tejasbubane][]) * [#20](https://github.com/rubocop-hq/rubocop-ast/pull/20): Add option predicates for `RegexpNode`. ([@owst][]) * [#11](https://github.com/rubocop-hq/rubocop-ast/issues/11): Add `argument_type?` method to make it easy to recognize argument nodes. ([@tejasbubane][]) * [#31](https://github.com/rubocop-hq/rubocop-ast/pull/31): NodePattern now uses `param === node` to match params, which allows Regexp, Proc, Set in addition to Nodes and literals. ([@marcandre][]) * [#41](https://github.com/rubocop-hq/rubocop-ast/pull/41): Add `delimiters` and related predicates for `RegexpNode`. ([@owst][]) * [#46](https://github.com/rubocop-hq/rubocop-ast/pull/46): Basic support for [non-legacy AST output from parser](https://github.com/whitequark/parser/#usage). Note that there is no support (yet) in main RuboCop gem. Expect `emit_forward_arg` to be set to `true` in v1.0 ([@marcandre][]) * [#48](https://github.com/rubocop-hq/rubocop-ast/pull/48): Support `Parser::Ruby28` for Ruby 2.8 (3.0) parser (experimental). ([@koic][]) * [#35](https://github.com/rubocop-hq/rubocop-ast/pull/35): NodePattern now accepts `%named_param` and `%CONST`. The macros `def_node_matcher` and `def_node_search` accept default named parameters. ([@marcandre][]) ## 0.0.3 (2020-05-15) ### Changes * [#7](https://github.com/rubocop-hq/rubocop-ast/issues/7): Classes `NodePattern`, `ProcessedSource` and `Token` moved to `AST::NodePattern`, etc. The `rubocop` gem has aliases to ensure compatibility. ([@marcandre][]) * [#7](https://github.com/rubocop-hq/rubocop-ast/issues/7): `AST::ProcessedSource.from_file` now raises a `Errno::ENOENT` instead of a `RuboCop::Error`. ([@marcandre][]) ## 0.0.2 (2020-05-12) ### Bug fixes * [Perf #106](https://github.com/rubocop-hq/rubocop-performance#106): Fix RegexpNode#to_regexp where option is 'o' + any other. ([@marcandre][]) * Define `RuboCop::AST::Version::STRING`. ([@marcandre][]) ## 0.0.1 (2020-05-11) * Gem extracted from RuboCop. ([@marcandre][]) [@marcandre]: https://github.com/marcandre [@tejasbubane]: https://github.com/tejasbubane [@owst]: https://github.com/owst [@fatkodima]: https://github.com/fatkodima [@koic]: https://github.com/koic [@dvandersluis]: https://github.com/dvandersluis rubocop-ast-0.6.0/CONTRIBUTING.md000066400000000000000000000070131373365104000162070ustar00rootroot00000000000000# Contributing If you discover issues, have ideas for improvements or new features, please report them to the [issue tracker][1] of the repository or submit a pull request. Please, try to follow these guidelines when you do so. ## Issue reporting * Check that the issue has not already been reported. * Check that the issue has not already been fixed in the latest code (a.k.a. `master`). * Be clear, concise and precise in your description of the problem. * Open an issue with a descriptive title and a summary in grammatically correct, complete sentences. * Include the output of `rubocop -V`: ``` $ rubocop -V 0.50.0 (using Parser 2.4.0.0, running on ruby 2.4.2 x86_64-linux) ``` * Include any relevant code to the issue summary. ## Pull requests * Read [how to properly contribute to open source projects on GitHub][2]. * Fork the project. * Use a topic/feature branch to easily amend a pull request later, if necessary. * Write [good commit messages][3]. * Use the same coding conventions as the rest of the project. * Commit and push until you are happy with your contribution. * If your change has a corresponding open GitHub issue, prefix the commit message with `[Fix #github-issue-number]`. * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. * Add an entry to the [Changelog](CHANGELOG.md) accordingly. See [changelog entry format](#changelog-entry-format). * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. * Make sure the test suite is passing and the code you wrote doesn't produce RuboCop offenses (usually this is as simple as running `bundle exec rake`). * [Squash related commits together][5]. * Open a [pull request][4] that relates to *only* one subject with a clear title and description in grammatically correct, complete sentences. ### Changelog entry format Here are a few examples: ``` * [#716](https://github.com/rubocop-hq/rubocop-ast/issues/716): Fixed a regression in the auto-correction logic of `MethodDefParentheses`. ([@bbatsov][]) * New cop `ElseLayout` checks for odd arrangement of code in the `else` branch of a conditional expression. ([@bbatsov][]) * [#7542](https://github.com/rubocop-hq/rubocop-ast/pull/7542): **(Breaking)** Move `LineLength` cop from `Metrics` department to `Layout` department. ([@koic][]) ``` * Mark it up in [Markdown syntax][6]. * The entry line should start with `* ` (an asterisk and a space). * If the change has a related GitHub issue (e.g. a bug fix for a reported issue), put a link to the issue as `[#123](https://github.com/rubocop-hq/rubocop-ast/issues/123): `. * Describe the brief of the change. The sentence should end with a punctuation. * If this is a breaking change, mark it with `**(Breaking)**`. * At the end of the entry, add an implicit link to your GitHub user page as `([@username][])`. * If this is your first contribution to RuboCop project, add a link definition for the implicit link to the bottom of the changelog as `[@username]: https://github.com/username`. [1]: https://github.com/rubocop-hq/rubocop-ast/issues [2]: https://www.gun.io/blog/how-to-github-fork-branch-and-pull-request [3]: https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [4]: https://help.github.com/articles/about-pull-requests [5]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html [6]: https://daringfireball.net/projects/markdown/syntax rubocop-ast-0.6.0/Gemfile000066400000000000000000000020121373365104000152430ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec gem 'bump', require: false gem 'pry' gem 'rake', '~> 12.0' gem 'rspec', '~> 3.7' local_ast = File.expand_path('../rubocop', __dir__) if Dir.exist? local_ast gem 'rubocop', path: local_ast else gem 'rubocop', '~> 0.89' end gem 'rubocop-performance', '~> 1.0' gem 'rubocop-rspec', '~> 1.0' # Workaround for cc-test-reporter with SimpleCov 0.18. # Stop upgrading SimpleCov until the following issue will be resolved. # https://github.com/codeclimate/test-reporter/issues/418 gem 'simplecov', '~> 0.10', '< 0.18' local_gemfile = File.expand_path('Gemfile.local', __dir__) eval_gemfile local_gemfile if File.exist?(local_gemfile) # #### Do NOT add `rubocop` (or anything depending on `rubocop`) # to insure that this gem remains independent of `rubocop`. # Bundler loads all dependencies automatically, so specs wouldn't detect it if # we required a file from `rubocop` gem by mistake. # Note that `rake internal_investigation` will use `../rubocop` if present. rubocop-ast-0.6.0/Gemfile.ci000066400000000000000000000005541373365104000156460ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' # This Gemfile is not required from the main Gemfile by design gem 'rubocop-performance' gem 'rubocop-rspec' version = ENV['RUBOCOP_VERSION'] gem 'rubocop', version unless version == 'master' # For 'master', assume that the latest gems are compatible # and that action script will install from source rubocop-ast-0.6.0/LICENSE.txt000066400000000000000000000020461373365104000156020ustar00rootroot00000000000000Copyright (c) 2012-20 Bozhidar Batsov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rubocop-ast-0.6.0/README.md000066400000000000000000000042501373365104000152350ustar00rootroot00000000000000# RuboCop AST [![Gem Version](https://badge.fury.io/rb/rubocop-ast.svg)](https://badge.fury.io/rb/rubocop-ast) [![CI](https://github.com/rubocop-hq/rubocop-ast/workflows/CI/badge.svg)](https://github.com/rubocop-hq/rubocop-ast/actions?query=workflow%3ACI) [![Test Coverage](https://api.codeclimate.com/v1/badges/a29666e6373bc41bc0a9/test_coverage)](https://codeclimate.com/github/rubocop-hq/rubocop-ast/test_coverage) [![Maintainability](https://api.codeclimate.com/v1/badges/a29666e6373bc41bc0a9/maintainability)](https://codeclimate.com/github/rubocop-hq/rubocop-ast/maintainability) Contains the classes needed by [RuboCop](https://github.com/rubocop-hq/rubocop) to deal with Ruby's AST, in particular: * `RuboCop::AST::Node` ([doc](docs/modules/ROOT/pages/node_types.adoc)) * `RuboCop::AST::NodePattern` ([doc](docs/modules/ROOT/pages/node_pattern.adoc)) This gem may be used independently from the main RuboCop gem. It was extracted from RuboCop in version 0.84 and its only dependency is the [parser](https://github.com/whitequark/parser) gem, which `rubocop-ast` extends. ## Installation Just install the `rubocop-ast` gem ```sh gem install rubocop-ast ``` or if you use bundler put this in your `Gemfile` ```ruby gem 'rubocop-ast' ``` ## Usage Refer to the documentation of [`RuboCop::AST::Node`](docs/modules/ROOT/pages/node_types.adoc) and [`RuboCop::AST::NodePattern`](docs/modules/ROOT/pages/node_pattern.adoc) See the [docs site](https://docs.rubocop.org/rubocop-ast) for more details. ### Parser compatibility switches This gem, by default, uses most [legacy AST output from parser](https://github.com/whitequark/parser/#usage), except for `emit_forward_arg` which is set to `true`. The main `RuboCop` gem uses these defaults (and is currently only compatible with these), but this gem can be used separately from `RuboCop` and is meant to be compatible with all settings. For example, to have `-> { ... }` emitted as `LambdaNode` instead of `SendNode`: ```ruby RuboCop::AST::Builder.emit_lambda = true ``` ## Contributing Checkout the [contribution guidelines](CONTRIBUTING.md). ## License `rubocop-ast` is MIT licensed. [See the accompanying file](LICENSE.txt) for the full text. rubocop-ast-0.6.0/Rakefile000066400000000000000000000015461373365104000154300ustar00rootroot00000000000000# frozen_string_literal: true require 'bundler' require 'bundler/gem_tasks' Dir['tasks/**/*.rake'].each { |t| load t } begin Bundler.setup rescue Bundler::BundlerError => e warn e.message warn 'Run `bundle install` to install missing gems' exit e.status_code end require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = FileList['spec/**/*_spec.rb'] end desc 'Run RSpec with code coverage' task :coverage do ENV['COVERAGE'] = 'true' Rake::Task['spec'].execute end desc 'Run RuboCop over itself' task :internal_investigation do Bundler.with_unbundled_env do local = '../rubocop/exe/rubocop' exe = if File.exist?(local) "bundle exec --gemfile=../rubocop/Gemfile #{local}" else 'rubocop' end sh exe end end task default: %i[ spec internal_investigation ] rubocop-ast-0.6.0/docs/000077500000000000000000000000001373365104000147055ustar00rootroot00000000000000rubocop-ast-0.6.0/docs/antora.yml000066400000000000000000000001211373365104000167060ustar00rootroot00000000000000name: rubocop-ast title: RuboCop AST version: '0.6' nav: - modules/ROOT/nav.adoc rubocop-ast-0.6.0/docs/modules/000077500000000000000000000000001373365104000163555ustar00rootroot00000000000000rubocop-ast-0.6.0/docs/modules/ROOT/000077500000000000000000000000001373365104000171405ustar00rootroot00000000000000rubocop-ast-0.6.0/docs/modules/ROOT/nav.adoc000066400000000000000000000002111373365104000205460ustar00rootroot00000000000000* xref:index.adoc[Home] * xref:installation.adoc[Installation] * xref:node_types.adoc[Node Types] * xref:node_pattern.adoc[Node Pattern] rubocop-ast-0.6.0/docs/modules/ROOT/pages/000077500000000000000000000000001373365104000202375ustar00rootroot00000000000000rubocop-ast-0.6.0/docs/modules/ROOT/pages/index.adoc000066400000000000000000000056351373365104000222070ustar00rootroot00000000000000= RuboCop AST This gem introduces two core classes of RuboCop: * `RuboCop::AST::Node` - this is an extension of the https://github.com/whitequark/parser/[`parser`] gem's `Node` class, which adds a simpler and more powerful object-oriented API to make it easier to work with nodes. * `RuboCop::AST::NodePattern` - a regular expression-style method to traverse and match nodes in an Abstract Syntax Tree. See xref:node_pattern.adoc["Node Pattern"] to get yourself familiar with ``NodePattern``'s capabilities. NOTE: This gem may be used independently from the main RuboCop gem. It was extracted from RuboCop in version 0.84 and its only dependency is the https://github.com/whitequark/parser[parser] gem, which `rubocop-ast` extends. == Rationale While working with ``parser``'s AST representation is fairly easy (especially when compared to the AST of Ruby's built-in `ripper` library), there's still areas we felt could be improved: * the canonical way to work with an AST node is to deconstruct the node in array-like fashion, which results in code that's hard to read * looking for complex AST node patterns requires a lot of boilerplate code * there's no easy way to tell apart AST nodes of certain types - e.g. prefix vs postfix conditionals * there's no easy way to grab the parent node of some node Enter `rubocop-ast`, which aims to solve those problems. This library evolved for years as part of RuboCop and was eventually spun off in the hope that it might be useful for other projects built on top of `parser`. `RuboCop::AST::Node` provides a wrapper around ``parser``'s `Node` class (in other words, `RuboCop::AST::Node < Parser::AST::Node`). In addition to a number of methods to make it easier to work with, the wrapper class also provides ways to inspect the *parents* of nodes, which the `parser` nodes do not support. Here are a few examples using `parser` and `rubocop-ast`: [cols="a,a"] |====================== |`parser`|`rubocop-ast` a| [source,ruby] ---- # type = :if is_if = node.loc.keyword == 'if' if_branch = node.children[1] else_branch = node.children[2] has_elsif_branch = node.children[2].type == :if && node.children[2].keyword == 'elsif' ---- a| [source,ruby] ---- # type = :if is_if = node.if? if_branch = node.if_branch else_branch = node.else_branch has_elsif_branch = node.elsif_conditional? ---- a| [source,ruby] ---- # type = :hash pairs = node.children pairs.each do \|pair_node\| key = pair_node.children[0] value = pair_node.children[1] do_something(key, value) end ---- a| [source,ruby] ---- # type = :hash node.each_pair do \|pair_node\| do_something(pair_node.key, pair_node.value) end ---- |====================== Sample usage: [source,ruby] ---- class MyRule < Parser::AST::Processor include RuboCop::AST::Traversal def on_sym(node) puts "I found a symbol! #{node.value}" end end source = RuboCop::ProcessedSource.new(code, 2.7) rule = MyRule.new source.ast.each_node { |n| rule.process(n) } ---- rubocop-ast-0.6.0/docs/modules/ROOT/pages/installation.adoc000066400000000000000000000003131373365104000235650ustar00rootroot00000000000000= Installation *RuboCop AST*'s installation is pretty standard: [source,sh] ---- $ gem install rubocop-ast ---- Using `bundler`, include it in your `Gemfile`: [source,rb] ---- gem 'rubocop-ast' ---- rubocop-ast-0.6.0/docs/modules/ROOT/pages/node_pattern.adoc000066400000000000000000000332221373365104000235530ustar00rootroot00000000000000= Node Pattern Node pattern is a DSL to help find specific nodes in the Abstract Syntax Tree using a simple string. It reminds the simplicity of regular expressions but used to find specific nodes of Ruby code. == History The Node Pattern was introduced by https://github.com/alexdowad[Alex Dowad] and solves a problem that RuboCop contributors were facing for a long time: * Ability to declaratively define rules for node search, matching, and capture. The code below belongs to https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/ArrayJoin[Style/ArrayJoin] cop and it's in favor of `Array#join` over `Array#*`. Then it tries to find code like `%w(one two three) * ", "` and suggest to use `#join` instead. It can also be an array of integers, and the code doesn't check it. However, it checks if the argument sent is a string. [source,ruby] ---- def on_send(node) receiver_node, method_name, *arg_nodes = *node return unless receiver_node && receiver_node.array_type? && method_name == :* && arg_nodes.first.str_type? add_offense(node, location: :selector) end ---- This code was replaced in the cop defining a new matcher that does the same as the code above: [source,ruby] ---- def_node_matcher :join_candidate?, '(send $array :* $str)' ---- And the `on_send` method is simplified to a method usage: [source,ruby] ---- def on_send(node) join_candidate?(node) { add_offense(node, location: :selector) } end ---- == `(` and `)` Navigate deeply with Parens Parens delimits navigation inside node and its children. A simple integer like `1` is represented by `(int 1)` in the AST. [source,sh] ---- $ ruby-parse -e '1' (int 1) ---- * `int` will match exactly the node, looking only the node type. * `(int 1)` will match precisely the node == `_` for any single node `_` will check if there's something present in the specific position, no matter the value: * `(int _)` will match any number * `(int _ _)` will not match because `int` types have just one child that contains the value. == `+...+` for several subsequent nodes Where `_` matches any single node, `+...+` matches any number of nodes. Say for example you want to find instances of calls to the method `sum` with any number of arguments, be it `sum(1, 2)` or `sum(1, 2, 3, n)`. First, let's check how it looks like in the AST: [source,sh] ---- $ ruby-parse -e 'sum(1, 2)' (send nil :sum (int 1) (int 2)) ---- Or with more children: [source,sh] ---- $ ruby-parse -e 'sum(1, 2, 3, n)' (send nil :sum (int 1) (int 2) (int 3) (send nil :n)) ---- The following expression would only match a call with 2 arguments: ---- (send nil? :sum _ _) ---- Instead, the following expression will any number of arguments (and thus both examples above): ---- (send nil? :sum ...) ---- Note that `+...+` can be appear anywhere in a sequence, for example `+(send nil? :sum ... int)+` would no longer match the second example, as the last argument is not an integer. Nesting `+...+` is also supported; the only limitation is that `+...+` and other "variable length" patterns can only appear once within a sequence. For example `+(send ... :sum ...)+` is not supported. == `*`, `+`, `?` for repetitions Another way to handle a variable number of nodes is by using `*`, `+`, `?` to signify a particular pattern should match any number of times, at least once and at most once respectively. Following on the previous example, to find sums of integer literals, we could use: ---- (send nil? :sum int*) ---- This would match our first example `sum(1, 2)` but not the other `sum(1, 2, 3, n)` This pattern would also match a call to `sum` without any argument, which might not be desirable. Using `+` would insure that only sums with at least one argument would be matched. ---- (send nil? :sum int+) ---- The `?` can limit the match only 0 or 1 nodes. The following example would match any sum of three integer literals optionally followed by a method call: ---- (send nil? :sum int int int send ?) ---- Note that we have to put a space between `send` and `?`, since `send?` would be considered as a predicate (described below). == `<>` for match in any order You may not care about the exact order of the nodes you want to match. In this case you can put the nodes without brackets: ---- (send nil? :sum <(int 2) int>) ---- This will match our first example (`sum(1, 2)`). It won't match our second example though, as it specifies that there must be exactly two arguments to the method call `sum`. You can add `+...+` before the closing bracket to allow for additional parameters: ---- (send nil? :sum <(int 2) int ...>) ---- This will match both our examples, but not `sum(1.0, 2)` or `sum(2)`, since the first node in the brackets is found, but not the second (`int`). == `{}` for "OR" Lets make it a bit more complex and introduce floats: [source,sh] ---- $ ruby-parse -e '1' (int 1) $ ruby-parse -e '1.0' (float 1.0) ---- * `({int float} _)` - int or float types, no matter the value == `[]` for "AND" Imagine you want to check if the number is `odd?` and also positive numbers: `(int [odd? positive?])` - is an int and the value should be odd and positive. == `$` for captures You can capture elements or nodes along with your search, prefixing the expression with `$`. For example, in a tuple like `(int 1)`, you can capture the value using `(int $_)`. You can also capture multiple things like: ---- (${int float} $_) ---- The tuple can be entirely captured using the `$` before the open parens: ---- $({int float} _) ---- Or remove the parens and match directly from node head: ---- ${int float} ---- All variable length patterns (`+...+`, `*`, `+`, `?`, `<>`) are captured as arrays. The following pattern will have two captures, both arrays: ---- (send nil? $int+ (send $...)) ---- == `^` for parent One may use the `^` character to check against a parent. For example, the following pattern would find any node with two children and with a parent that is a hash: ---- (^hash _key $_value) ---- It is possible to use `^` somewhere else than the head of a sequnece; in that case it is relative to that child (i.e. the current node). One case also use multiple `^` to go up multiple levels. For example, the previous example is basically the same as: ---- (pair ^^hash $_value) ---- == ``` for descendants The ``` character can be used to search a node and all its descendants. For example if looking for a `return` statement anywhere within a method definition, we can write: ---- (def _method_name _args `return) ---- This would match both of these methods `foo` and `bar`, even though these `return` for `foo` and `bar` are not at the same level. ---- def foo # (def :foo return 42 # (args) end # (return # (int 42))) def bar # (def :bar return 42 if foo # (args) nil # (begin end # (if # (send nil :foo) # (return # (int 42)) nil) # (nil))) ---- == Predicate methods Words which end with a `?` are predicate methods, are called on the target to see if it matches any Ruby method which the matched object supports can be used. Example: * `int_type?` can be used herein replacement of `(int _)`. And refactoring the expression to allow both int or float types: * `{int_type? float_type?}` can be used herein replacement of `({int float} _)` You can also use it at the node level, asking for each child: * `(int odd?)` will match only with odd numbers, asking it to the current number. == `#` to call functions Sometimes, we want to add extra logic. Let's imagine we're searching for prime numbers, so we have a method to detect it: [source,ruby] ---- def prime?(n) if n <= 1 false elsif n == 2 true else (2..n/2).none? { |i| n % i == 0 } end end ---- We can use the `#prime?` function directly in the expression: ---- (int #prime?) ---- == Arguments for predicate and function calls Arguments can be passed to predicates and function calls, like literals, parameters: [source,ruby] ---- def divisible_by?(value, divisor) value % divisor == 0 end ---- Example patterns using this function: ---- (int #divisible_by?(42)) (send (int _value) :+ (int #divisible_by?(_value)) ---- The arguments can be pattern themselves, in which case a matcher responding to `===` will be passed. This makes patterns composable: ```ruby def_node_matcher :global_const?, '(const {nil? cbase} %1)' def_node_matcher :class_creator, '(send #global_const?({:Class :Module}) :new ...)' ``` == Using node matcher macros The RuboCop base includes two useful methods to use the node pattern with Ruby in a simple way. You can use the macros to define methods. The basics are https://www.rubydoc.info/gems/rubocop/RuboCop/NodePattern/Macros#def_node_matcher-instance_method[def_node_matcher] and https://www.rubydoc.info/gems/rubocop/RuboCop/NodePattern/Macros#def_node_search-instance_method[def_node_search]. When you define a pattern, it creates a method that accepts a node and tries to match. Lets create an example where we're trying to find the symbols `user` and `current_user` in expressions like: `user: current_user` or `current_user: User.first`, so the objective here is pick all keys: [source,sh] ---- $ ruby-parse -e ':current_user' (sym :current_user) $ ruby-parse -e ':user' (sym :user) $ ruby-parse -e '{ user: current_user }' (hash (pair (sym :user) (send nil :current_user))) ---- Our minimal matcher can get it in the simple node `sym`: [source,ruby] ---- def_node_matcher :user_symbol?, '(sym {:current_user :user})' ---- === Composing complex expressions with multiple matchers Now let's go deeply combining the previous expression and also match if the current symbol is being called from an initialization method, like: [source,sh] ---- $ ruby-parse -e 'Comment.new(user: current_user)' (send (const nil :Comment) :new (hash (pair (sym :user) (send nil :current_user)))) ---- And we can also reuse this and check if it's a constructor: [source,ruby] ---- def_node_matcher :initializing_with_user?, <<~PATTERN (send _ :new (hash (pair #user_symbol?))) PATTERN ---- == `%` for arguments Arguments can be passed to matchers, either as external method arguments, or to be used to compare elements. An example of method argument: [source,ruby] ---- def multiple_of?(n, factor) n % factor == 0 end def_node_matcher :int_node_multiple?, '(int #multiple_of?(%1))' # ... int_node_multiple?(node, 10) # => true if node is an 'int' node with a multiple of 10 ---- Arguments can be used to match nodes directly: [source,ruby] ---- def_node_matcher :has_sensitive_data?, '(hash <(pair (_ %1) $_) ...>)' # ... has_user_data?(node, :password) # => true if node is a hash with a key +:password+ # matching uses ===, so to match strings or symbols, 'pass' or 'password' one can: has_user_data?(node, /^pass(word)?$/i) # one can also pass lambdas... has_user_data?(node, ->(key) { # return true or false depending on key }) ---- NOTE: `Array#===` will never match a single node element (so don't pass arrays), but `Set#===` is an alias to `Set#include?` (Ruby 2.5+ only), and so can be very useful to match within many possible literals / Nodes. == `%param_name` for named parameters Arguments can be passed as named parameters. They will be matched using `===` (see `%` above). Contrary to positional arguments, defaults values can be passed to `def_node_matcher` and `def_node_search`: [source,ruby] ---- def_node_matcher :interesting_call?, '(send _ %method ...)', method: Set[:transform_values, :transform_keys, :transform_values!, :transform_keys!, :to_h].freeze # Usage: interesting_call?(node) # use the default methods interesting_call?(node, method: /^transform/) # match anything starting with 'transform' ---- Named parameters as arguments to custom methods are also supported. == `%CONST` for constants Constants can be included in patterns. They will be matched using `===`, so +Regexp+ / +Set+ / +Proc+ can be used in addition to literals and +Nodes+: [source,ruby] ---- SOME_CALLS = Set[:transform_values, :transform_keys, :transform_values!, :transform_keys!, :to_h].freeze def_node_matcher :interesting_call?, '(send _ %SOME_CALLS ...)' ---- Constants as arguments to custom methods are also supported. == Comments You may have comments in node patterns at the end of lines by preceeding them with `'# '`: [source,ruby] ---- def_node_matcher :complex_stuff, <<~PATTERN (send {#global_const?(:Kernel) nil?} # check for explicit call like Kernel.p too {:p :pp} # let's consider `pp` also $... # capture all arguments ) PATTERN ---- == `nil` or `nil?` Take a special attention to nil behavior: [source,sh] ---- $ ruby-parse -e 'nil' (nil) ---- In this case, the `nil` implicit matches with expressions like: `nil`, `(nil)`, or `nil_type?`. But, nil is also used to represent a call from `nothing` from a simple method call: [source,sh] ---- $ ruby-parse -e 'method' (send nil :method) ---- Then, for such case you can use the predicate `nil?`. And the code can be matched with an expression like: ---- (send nil? :method) ---- == More resources Curious about how it works? Check more details in the https://www.rubydoc.info/gems/rubocop-ast/RuboCop/AST/NodePattern[documentation] or browse the https://github.com/rubocop-hq/rubocop-ast/blob/master/lib/rubocop/ast/node_pattern.rb[source code] directly. It's easy to read and hack on. The https://github.com/rubocop-hq/rubocop-ast/blob/master/spec/rubocop/ast/node_pattern_spec.rb[specs] are also very useful to comprehend each feature. rubocop-ast-0.6.0/docs/modules/ROOT/pages/node_types.adoc000066400000000000000000000472521373365104000232520ustar00rootroot00000000000000= Node Types This is a partial list of the node types parsed by the AST and corresponding methods and information associated with them. `RuboCop::AST::Node` defines some additional methods on certain node types by giving them a separate class. You can see the method definitions in the https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/Node[API documentation]. For full information, please see the https://github.com/whitequark/parser/blob/master/doc/AST_FORMAT.md[parser documentation]. This page will act as a quick index to that page, which has more examples and fuller explanation of the different location pieces. You can also see a full AST for yourself by running `ruby-parse --legacy -L -e "ruby(code: 'here')"`. There are a few "meta-types" of nodes that will be mentioned in descriptions: * Expression nodes: any expression that returns a value - variables (`lvar`, `ivar`, `cvar` etc.), `send` or `csend`, `const`, `self`, any literal value (`int`, `str`, `nil`, etc.), or control statements like `if`, `case`, `begin`, etc. * Assignment nodes: Any node that assigns a value. `lvasgn`, `ivasgn`, `cvasgn`, `gvasgn`, `send` (e.g. `self.foo = 5`), or `csend`. * Body statement: This can be essentially any node except those that must be nested (like `args` or `mlhs`). Typically it comes as the children of a node that can contain arbitrary code, like `def` or `class`. This will always be a single node which is either an expression, a `begin` node, or `nil`. == Location Information There are different parts of the source map you can get from the node, by calling `.loc` on it. Every node has an `expression` value which usually represents the entire node, but others have additional fields. The following fields are given when relevant to nodes in the source code: [cols="m,a"] |========= |Field|Description |assoc|The fat-arrow: `=>` used in the body of rescue clauses (`resbody`), not in a hash `pair`) |begin|* Start of parentheses or square brackets `(` or `[` * Start of `do..end` blocks (containing the `do` keyword) * `then` keyword * `begin` keyword * The first symbol in literals (like `"` for strings or `:` for symbols) |colon|The `:` symbol, used as part of a ternary (`:`) |dot|The `.` or `&.` operator used for `send` and `csend` nodes. |double_colon|The `::` operator |else|The `else` or `elsif` keyword |end|* End of parentheses or square brackets `)` or `]` * The `end` keyword * The last symbol in literals (like `"` for strings) |heredoc_body|The body of a string in heredoc format. |heredoc_end|The end of the heredoc statement. |in|The `in` keyword (as in `for..in`) |keyword|Any text-based keyword, like `begin` or `and`, with the exception of other keywords handled by other fields. This will include the full string containing the keyword. |name|Used when *defining* something (like `const` or `arg`). |operator|Any symbol representing an operator, like `*` (splat) or `\|\|=`. |question|The `?` symbol used as part of a ternary (`if`) |selector|Used for method invocation (`send` / `csend`) or operators that are actually methods (like `+`) |========= == Node Types [cols="m,a,a,m,m"] |============================================= |Type|Description|Children|Example|Node Class |alias|Method alias|Two children - both are `sym`, `dsym` or `gvar` nodes.|alias :foo :bar|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/AliasNode[AliasNode] |and|And operator|Two children are both expression nodes representing the operands.|a and b && c |https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/AndNode[AndNode] |and_asgn|And-assignment (AND the receiver with the argument and assign it back to receiver).|First child must be an assignment node, second child is the expression node.|a &&= b |N/A |arg|Required positional argument. Must come inside an `args`.|One child - a symbol, representing the argument name.|def foo(bar)|N/A |args|Argument list. Must come inside a `def`, `defs`, `def_e`, `defs_e` or `block` node.|Children must be `arg`, `optarg`, `restarg`, `blockarg`, `kwarg`, `kwoptarg`, `kwrestarg`, `kwnilarg`, or `forwardarg`.|def whatever(foo, bar=1, baz: 5)|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/ArgsNode[ArgsNode] |array|Array literal.|The values in the array, including a possible `splat`.|[1, 2, 3]|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/ArrayNode[ArrayNode] |back-ref|Regular expression back-reference, e.g. $&.|One child (symbol) representing the reference name, e.g. `:$&`.|re = /foo(abc)/; $&|N/A |block|Block execution.|Three children. First child is the receiver *or* a `lambda` node; second child is `args` or `forward_args` (only if `emit_forward` is false; it's true by default); third child is a body statement.|foo.bar do \|a, b\|; puts a; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/BlockNode[BlockNode] |block_pass|Used when passing a block as an argument.|One child, an expression node representing the block to pass.|foo(a, &my_block)|N/A |blockarg|Reference to block argument from a function definition. Must come inside an `args`.|One child - a symbol, representing the argument name.|def foo(&bar)|N/A |break|break keyword|One child with an expression node for the results to be passed through the break.|break 1|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/BreakNode[BreakNode] |case|Case statement.|First child is an expression node for the condition to check. Last child is an expression node for the "else" condition. All middle nodes are `when` nodes.|case a; when 1; b; when 2; c; else d; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/CaseNode[CaseNode] |casgn|Constant assignment|Three children: the parent object (either an expression, `nil` or `cbase`), the constant name (a symbol), and the expression being assigned.|Foo::Bar = 5|N/A |cbase|Represents the top-module constant (i.e. the '::' before a constant name). Only occurs inside a `const` node.|None|::Foo|N/A |complex|Complex literal|One child, the Complex value|1i|N/A |const|Constant reference.|Two children, the parent object (either an expression, `nil` or `cbase`) and the constant name (a symbol). |AModule::AClass|N/A |class|Class definition|Three children. First child is a `const` node for the class name, second child is a `const` node for the parent name, or `nil`, third child is a body statement.|class Foo < Bar; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/ClassNode[ClassNode] |csend|Null-safe method invocation, i.e. using `&.`|First child is the receiver node (e.g. `self`), second child is the method name (e.g. `:foo=`) and the remaining children (if any) are nodes representing arguments.|foo&.bar|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/SendNode[SendNode] |cvar|Class variable access|One child, the variable name `:@@cfoo`|@@cfoo|N/A |cvasgn|Class variable assignment|Two children: the variable name `:@@foo` and the expression being assigned|@@foo = 5|N/A |def|Instance method definition (full format)|Three children. First child is the name of the method (symbol); second child is `args` or `forward_args` (only if `emit_forward` is false, and it's true by default), and the last child is a body statement.|def foo(some_arg, kwarg: 1); end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/DefNode[DefNode] |defined?|`defined?` keyword.|One child, an expression.|defined?(foo)|N/A |defs|Singleton method definition (full format) - i.e. defining a method on a single object.|Four children. First child is the receiver; second child is the name of the method (symbol); third child is `args` or `forward_args` (only if `emit_forward` is false, and it's true by default), and the fourth child is a body statement.|def some_obj.foo(some_arg, kwarg: 1); end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/DefNode[DefNode] |dstr|Interpolated string literal.|Children are split into `str` nodes, with interpolation represented by separate expression nodes. |`"foo#{bar}baz"`|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/StrNode[StrNode] |dsym|Interpolated symbol literal.|Children are split into `str` nodes, with interpolation represented by separate expression nodes. |`:"foo#{bar}baz"`|N/A |ensure|Block that contains an `ensure` along with possible `rescue`s. Must be inside a `def`, `defs`, `block` or `begin`.|The last child is the body statement of the `ensure` block. If there is a `rescue`, it is the first child (and contains the body statement of the top block); otherwise, the first child is the body statement of the top block.|begin; foo; rescue Exception; bar; ensure; baz; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/EnsureNode[EnsureNode] |erange|Exclusive range literal|Two children, the start and end nodes (including `nil` for beginless/endless)|1...2|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/RangeNode[RangeNode] |false|False literal|None|false|N/A |float|Floating point literal|One child, the Float value|-123.5|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/FloatNode[FloatNode] |for|for..in looping condition|Three children. First child is a `lvasgn` or `mlhs` node with the variable(s), second child is an expression node with the array/range to loop over, third child is a body statement.|for a in arr do foo; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/ForNode[ForNode] |forward_arg|Forwarding argument, for Ruby 2.8 (when `emit_forward_arg` is true). Must come inside an `args` node.|None|def whatever(foo, ...)|N/A |forward_args|Forwarding argument list, for Ruby 2.7 (when `emit_forward_arg` is false). Must come inside a `def`, `defs`, `def_e`, or `defs_e` node.|None|def (foo(...)|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/ForwardArgsNode[ForwardArgsNode] |forwarded-args|Forwarding arguments into a method call|None|foo(...)|N/A |gvar|Global variable access|One child, the variable name as a symbol `:$foo`|$foo|N/A |gvasgn|Global variable assignment|Two children, the variable name `:$foo` and the expression being assigned|$foo = 5|N/A |hash|Hash literal.|`pair` s and/or `kwsplat` s.|{ foo: 'bar' }|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/HashNode[HashNode] |if|If, else, elif, unless and ternary conditions|Three children. First child is the expression node representing the condition; second child is an expression node representing the true condition; third child is an expression, node representing the false condition. `elif` will nest another `if` node as the third child. `question` and `colon` location keys will only exist for ternaries.|if foo; bar; else; baz; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/IfNode[IfNode] |int|Integer literal|1, the integer value|-123|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/IntNode[IntNode] |ivar|Instance variable access|One child, the variable name `:@foo`|@foo|N/A |ivasgn|Instance variable assignment|Two children, the variable name `:@foo` and the expression being assigned|@foo = 5|N/A |irange|Inclusive range literal.|Two children, the start and end nodes (including `nil` for beginless/endless)|1..2|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/RangeNode[RangeNode] |kwarg|Required keyword argument. Must come inside an `args`.|One child - a symbol, representing the argument name.|def foo(bar:)|N/A |kwbegin|Explicit `begin` block.|Child nodes are body statements.|begin,end|N/A |kwnilarg|Double splat with nil in function definition, used to specify that the function does not accept keyword args. Must come inside an `args`.|None|def foo(**nil)|N/A |kwoptarg|Optional keyword argument. Must come inside an `args`.|Two children - a symbol, representing the argument name, and an expression node for the value.|def foo(bar: 5)|N/A |kwsplat|Double splat used for keyword arguments inside a function call (as opposed to a function definition).|One child, an expression.|foo(bar, **kwargs)|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/KeywordSplatNode[KeywordSplatNode] |kwrestargs|Double splat used for keyword arguments inside a function definition (as opposed to a function call). Must come inside an `args`.|One child - a symbol, representing the argument name, if a name is given. If no name given, it has no children..|def foo(**kwargs)|N/A |lvar|Local variable access|One child, the variable name|foo|N/A |lvasgn|Local variable assignment|Two children: The variable name (symbol) and the expression.|a = some_thing|N/A |masgn|Multiple assigment.|First set of children are all `mlhs` nodes, and the rest of the children must be expression nodes corresponding to the values in the `mlhs` nodes.|a, b, = [1, 2]|N/A |mlhs|Multiple left-hand side. Only used inside a `masgn`.|Children must all be assignment nodes. Represents the left side of a multiple assignment (`a, b` in the example).|a, b = 5, 6|N/A |module|Module definition|Two children. First child is a `const` node for the module name. Second child is a body statement.|module Foo < Bar; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/ModuleNode[ModuleNode] |next|next keyword|Zero or one child with an expression node for the results to be passed through the next|next 1|N/A |nil|Nil literal|None|nil|N/A |nth-ref|Regular expression capture group ($1, $2 etc.)|One child: The capture name, e.g. `:$1`|re = /foo(abc)/; $1|N/A |numblock|Block that has numbered arguments (`_1`) referenced inside it.|Three children. First child is a `send`/`csend` node representing the way the block is created, second child is an `int` (the number of numeric arguments) and the third child is a body statement.|proc { _1 + _3 }|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/BlockNode[BlockNode] |op_asgn|Operator-assignment - perform an operation and assign the value.|Three children. First child must be an assignment node, second child is the operator (e.g. `:+`) and the third child is the expression node.|a += b|N/A |opt_arg|Optional positional argument. Must come inside an `args`.|One child - a symbol, representing the argument name.|def foo(bar=1)|N/A |or|Or operator|Two children are both expression nodes representing the operands.|a or b|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/OrNode[OrNode] |or_asgn|Or-assignment (OR the receiver with the argument and assign it back to receiver).|Two children. First child must be an assignment node, second child is the expression node.|a \|\|= b|N/A |pair|One entry in a hash. |Two children, the key and value nodes.|1 => 2|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/PairNode[PairNode] |rasgn|Right-hand assignment|Two children, the node representing the value to assign and the assignment node.|1 => a | |rational|Rational literal|One child, the Rational value|2.0r|N/A |redo|Redo command|None|redo|N/A |regexp|Regular expression literal.|Children are split into `str` nodes, with interpolation represented by separate expression nodes. The last child is a `regopt`.|/foo#{bar}56/|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/RegexpNode[RegexpNode] |regopt|Regular expression option, appearing after a regexp literal (the "im" in the example).|A list of symbols representing the options (e.g. `:i` and `:m`) |/foo#{bar}/im|N/A |resbody|Exception rescue. Always occurs inside a `rescue` node.|Three children. First child is either `nil` or an array of expression nodes representing the exceptions to rescue. Second child is `nil` or an assignment node representing the value to save the exception into. Last child is a body statement.|begin; rescue Exception, A => bar; 1; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/ResbodyNode[ResbodyNode] |rescue|A rescue statement.May be "top-level" or may be nested inside an `ensure` block (if both rescue and ensure are in the block).|First node is a body statement. Last child is the "else" body statement, or `nil`. Remaining children are `resbody` nodes.|begin; rescue Exception, A => bar; 1; end| |restarg|Positional splat argument. Must come inside an `args`.|One child - a symbol, representing the argument name (if given). If no name given, there are no children.|def foo(*rest)|N/A |return|Return statement|Zero or one child, an expression node for the value to return.|return|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/ReturnNode[ReturnNode] |sclass|Singleton class declaration.|Two children. The first child is the expression for the class being opened (e.g. `self`); second child is a body statement.|class << some_var|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/SelfClassNode[SelfClassNode] |self|Access to self|None|self|N/A |send|Non-safe method invocation (i.e. top-level or using a dot)|First child is the receiver node (e.g. `self`), second child is the method name (e.g. `:foo=`) and the remaining children (if any) are the arguments (expression nodes). a|`foo` or `foo.bar`|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/SendNode[SendNode] |splat|Array or function argument * operator|One child, an expression.|*foo|N/A |str|Non-interpolated string literal. The heredoc version works very differently from the regular version and the location info is totally separate.|One child, the String content. |"hi mom"|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/StrNode[StrNode] |super|Super method call with arguments and/or brackets.|Children are expression nodes representing arguments.|super(a, b, c)|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/SuperNode[SuperNode] |sym|Non-interpolated symbol|One child, the Symbol content.|`:foo`|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/SymbolNode[SymbolNode] |true|True literal|None|true|N/A |undef|Method undefinition|A list of `sym`, or `dsym` nodes representing method names to undefine.|undef :foo, :bar|N/A |until|Negative loop with condition coming first.|Two children. First child is an expression node for condition, second child is a body statement.|until foo do bar; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/UntilNode[UntilNode] |until_post|Negative loop with condition coming last.|Two children. First child is an expression node for condition, second child is a body statement.|begin; foo; end until condition|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/UntilNode[UntilNode] |when|Case matching. Usually nested under `case` nodes.|Two children. First child is a regexp, expression node, or `splat` node for the condition. Second child is an expression node or `begin` node for the results.|when a then b|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/WhenNode[WhenNode] |while|Loop with condition coming first.|Two children. First child is an expression node for condition, second child is a body statement.|while foo do bar; end|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/WhileNode[WhileNode] |while-post|Loop with condition coming last.|Two children. First child is an expression node for condition, second child is a body statement.|begin; foo; end while condition|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/WhileNode[WhileNode] |xstr|Execute string (backticks). The heredoc version is treated totally differently from the regular version.|Children are split into `str` nodes, with interpolation represented by separate expression nodes .|`foo#{bar}`|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/StrNode[StrNode] |yield|Yield to a block.|Children are expression nodes representing arguments.|yield(foo)|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/YieldNode[YieldNode] |zsuper|Super method call with no arguments or brackets.|None|super|https://rubydoc.info/github/rubocop-hq/rubocop-ast/RuboCop/AST/SuperNode[SuperNode] |============================================= rubocop-ast-0.6.0/lib/000077500000000000000000000000001373365104000145235ustar00rootroot00000000000000rubocop-ast-0.6.0/lib/rubocop-ast.rb000066400000000000000000000000761373365104000173110ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'rubocop/ast' rubocop-ast-0.6.0/lib/rubocop/000077500000000000000000000000001373365104000161745ustar00rootroot00000000000000rubocop-ast-0.6.0/lib/rubocop/ast.rb000066400000000000000000000051331373365104000173120ustar00rootroot00000000000000# frozen_string_literal: true require 'parser' require 'forwardable' require 'set' require_relative 'ast/ext/range' require_relative 'ast/ext/set' require_relative 'ast/node_pattern' require_relative 'ast/sexp' require_relative 'ast/node' require_relative 'ast/node/mixin/method_identifier_predicates' require_relative 'ast/node/mixin/binary_operator_node' require_relative 'ast/node/mixin/collection_node' require_relative 'ast/node/mixin/conditional_node' require_relative 'ast/node/mixin/hash_element_node' require_relative 'ast/node/mixin/method_dispatch_node' require_relative 'ast/node/mixin/modifier_node' require_relative 'ast/node/mixin/numeric_node' require_relative 'ast/node/mixin/parameterized_node' require_relative 'ast/node/mixin/predicate_operator_node' require_relative 'ast/node/mixin/basic_literal_node' require_relative 'ast/node/alias_node' require_relative 'ast/node/and_node' require_relative 'ast/node/args_node' require_relative 'ast/node/array_node' require_relative 'ast/node/block_node' require_relative 'ast/node/break_node' require_relative 'ast/node/case_match_node' require_relative 'ast/node/case_node' require_relative 'ast/node/class_node' require_relative 'ast/node/const_node' require_relative 'ast/node/def_node' require_relative 'ast/node/defined_node' require_relative 'ast/node/ensure_node' require_relative 'ast/node/for_node' require_relative 'ast/node/forward_args_node' require_relative 'ast/node/float_node' require_relative 'ast/node/hash_node' require_relative 'ast/node/if_node' require_relative 'ast/node/index_node' require_relative 'ast/node/indexasgn_node' require_relative 'ast/node/int_node' require_relative 'ast/node/keyword_splat_node' require_relative 'ast/node/lambda_node' require_relative 'ast/node/module_node' require_relative 'ast/node/next_node' require_relative 'ast/node/or_node' require_relative 'ast/node/pair_node' require_relative 'ast/node/range_node' require_relative 'ast/node/regexp_node' require_relative 'ast/node/rescue_node' require_relative 'ast/node/resbody_node' require_relative 'ast/node/return_node' require_relative 'ast/node/self_class_node' require_relative 'ast/node/send_node' require_relative 'ast/node/str_node' require_relative 'ast/node/super_node' require_relative 'ast/node/symbol_node' require_relative 'ast/node/until_node' require_relative 'ast/node/when_node' require_relative 'ast/node/while_node' require_relative 'ast/node/yield_node' require_relative 'ast/builder' require_relative 'ast/processed_source' require_relative 'ast/rubocop_compatibility' require_relative 'ast/token' require_relative 'ast/traversal' require_relative 'ast/version' rubocop-ast-0.6.0/lib/rubocop/ast/000077500000000000000000000000001373365104000167635ustar00rootroot00000000000000rubocop-ast-0.6.0/lib/rubocop/ast/builder.rb000066400000000000000000000053211373365104000207370ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # `RuboCop::AST::Builder` is an AST builder that is utilized to let `Parser` # generate ASTs with {RuboCop::AST::Node}. # # @example # buffer = Parser::Source::Buffer.new('(string)') # buffer.source = 'puts :foo' # # builder = RuboCop::AST::Builder.new # require 'parser/ruby25' # parser = Parser::Ruby25.new(builder) # root_node = parser.parse(buffer) class Builder < Parser::Builders::Default self.emit_forward_arg = true NODE_MAP = { and: AndNode, alias: AliasNode, args: ArgsNode, array: ArrayNode, block: BlockNode, numblock: BlockNode, break: BreakNode, case_match: CaseMatchNode, case: CaseNode, class: ClassNode, const: ConstNode, def: DefNode, defined?: DefinedNode, defs: DefNode, ensure: EnsureNode, for: ForNode, forward_args: ForwardArgsNode, float: FloatNode, hash: HashNode, if: IfNode, int: IntNode, index: IndexNode, indexasgn: IndexasgnNode, irange: RangeNode, erange: RangeNode, kwsplat: KeywordSplatNode, lambda: LambdaNode, module: ModuleNode, next: NextNode, or: OrNode, pair: PairNode, regexp: RegexpNode, rescue: RescueNode, resbody: ResbodyNode, return: ReturnNode, csend: SendNode, send: SendNode, str: StrNode, dstr: StrNode, xstr: StrNode, sclass: SelfClassNode, super: SuperNode, zsuper: SuperNode, sym: SymbolNode, until: UntilNode, until_post: UntilNode, when: WhenNode, while: WhileNode, while_post: WhileNode, yield: YieldNode }.freeze # Generates {Node} from the given information. # # @return [Node] the generated node def n(type, children, source_map) node_klass(type).new(type, children, location: source_map) end # TODO: Figure out what to do about literal encoding handling... # More details here https://github.com/whitequark/parser/issues/283 def string_value(token) value(token) end private def node_klass(type) NODE_MAP[type] || Node end end end end rubocop-ast-0.6.0/lib/rubocop/ast/ext/000077500000000000000000000000001373365104000175635ustar00rootroot00000000000000rubocop-ast-0.6.0/lib/rubocop/ast/ext/range.rb000066400000000000000000000014201373365104000212010ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST module Ext # Extensions to Parser::AST::Range module Range # @return [Range] the range of line numbers for the node # If `exclude_end` is `true`, then the range will be exclusive. # # Assume that `node` corresponds to the following array literal: # # [ # :foo, # :bar # ] # # node.loc.begin.line_span # => 1..1 # node.loc.expression.line_span(exclude_end: true) # => 1...4 def line_span(exclude_end: false) ::Range.new(first_line, last_line, exclude_end) end end end end end ::Parser::Source::Range.include ::RuboCop::AST::Ext::Range rubocop-ast-0.6.0/lib/rubocop/ast/ext/set.rb000066400000000000000000000002511373365104000207010ustar00rootroot00000000000000# frozen_string_literal: true test = :foo case test when Set[:foo] # ok, RUBY_VERSION > 2.4 else # Harmonize `Set#===` class Set alias === include? end end rubocop-ast-0.6.0/lib/rubocop/ast/node.rb000066400000000000000000000543031373365104000202420ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # `RuboCop::AST::Node` is a subclass of `Parser::AST::Node`. It provides # access to parent nodes and an object-oriented way to traverse an AST with # the power of `Enumerable`. # # It has predicate methods for every node type, like this: # # @example # node.send_type? # Equivalent to: `node.type == :send` # node.op_asgn_type? # Equivalent to: `node.type == :op_asgn` # # # Non-word characters (other than a-zA-Z0-9_) in type names are omitted. # node.defined_type? # Equivalent to: `node.type == :defined?` # # # Find the first lvar node under the receiver node. # lvar_node = node.each_descendant.find(&:lvar_type?) # class Node < Parser::AST::Node # rubocop:disable Metrics/ClassLength include RuboCop::AST::Sexp extend NodePattern::Macros # <=> isn't included here, because it doesn't return a boolean. COMPARISON_OPERATORS = %i[== === != <= >= > <].freeze TRUTHY_LITERALS = %i[str dstr xstr int float sym dsym array hash regexp true irange erange complex rational regopt].freeze FALSEY_LITERALS = %i[false nil].freeze LITERALS = (TRUTHY_LITERALS + FALSEY_LITERALS).freeze COMPOSITE_LITERALS = %i[dstr xstr dsym array hash irange erange regexp].freeze BASIC_LITERALS = (LITERALS - COMPOSITE_LITERALS).freeze MUTABLE_LITERALS = %i[str dstr xstr array hash regexp irange erange].freeze IMMUTABLE_LITERALS = (LITERALS - MUTABLE_LITERALS).freeze EQUALS_ASSIGNMENTS = %i[lvasgn ivasgn cvasgn gvasgn casgn masgn rasgn mrasgn].freeze SHORTHAND_ASSIGNMENTS = %i[op_asgn or_asgn and_asgn].freeze ASSIGNMENTS = (EQUALS_ASSIGNMENTS + SHORTHAND_ASSIGNMENTS).freeze BASIC_CONDITIONALS = %i[if while until].freeze CONDITIONALS = [*BASIC_CONDITIONALS, :case].freeze POST_CONDITION_LOOP_TYPES = %i[while_post until_post].freeze LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + %i[while until for]).freeze VARIABLES = %i[ivar gvar cvar lvar].freeze REFERENCES = %i[nth_ref back_ref].freeze KEYWORDS = %i[alias and break case class def defs defined? kwbegin do else ensure for if module next not or postexe redo rescue retry return self super zsuper then undef until when while yield].freeze OPERATOR_KEYWORDS = %i[and or].freeze SPECIAL_KEYWORDS = %w[__FILE__ __LINE__ __ENCODING__].freeze ARGUMENT_TYPES = %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg].freeze # @see https://www.rubydoc.info/gems/ast/AST/Node:initialize def initialize(type, children = [], properties = {}) @mutable_attributes = {} # ::AST::Node#initialize freezes itself. super # #parent= may be invoked multiple times for a node because there are # pending nodes while constructing AST and they are replaced later. # For example, `lvar` and `send` type nodes are initially created as an # `ident` type node and fixed to the appropriate type later. # So, the #parent attribute needs to be mutable. each_child_node do |child_node| child_node.parent = self unless child_node.complete? end end Parser::Meta::NODE_TYPES.each do |node_type| method_name = "#{node_type.to_s.gsub(/\W/, '')}_type?" define_method(method_name) do type == node_type end end # Returns the parent node, or `nil` if the receiver is a root node. # # @return [Node, nil] the parent node or `nil` def parent @mutable_attributes[:parent] end def parent=(node) @mutable_attributes[:parent] = node end # @return [Boolean] def parent? !!parent end # @return [Boolean] def root? !parent end def complete! @mutable_attributes.freeze each_child_node(&:complete!) end def complete? @mutable_attributes.frozen? end protected :parent= # Override `AST::Node#updated` so that `AST::Processor` does not try to # mutate our ASTs. Since we keep references from children to parents and # not just the other way around, we cannot update an AST and share # identical subtrees. Rather, the entire AST must be copied any time any # part of it is changed. def updated(type = nil, children = nil, properties = {}) properties[:location] ||= @location klass = RuboCop::AST::Builder::NODE_MAP[type || @type] || Node klass.new(type || @type, children || @children, properties) end # Returns the index of the receiver node in its siblings. (Sibling index # uses zero based numbering.) # Use is discouraged, this is a potentially slow method. # # @return [Integer, nil] the index of the receiver node in its siblings def sibling_index parent&.children&.index { |sibling| sibling.equal?(self) } end # Use is discouraged, this is a potentially slow method and can lead # to even slower algorithms # @return [Node, nil] the right (aka next) sibling def right_sibling return unless parent parent.children[sibling_index + 1].freeze end # Use is discouraged, this is a potentially slow method and can lead # to even slower algorithms # @return [Node, nil] the left (aka previous) sibling def left_sibling i = sibling_index return if i.nil? || i.zero? parent.children[i - 1].freeze end # Use is discouraged, this is a potentially slow method and can lead # to even slower algorithms # @return [Array] the left (aka previous) siblings def left_siblings return [].freeze unless parent parent.children[0...sibling_index].freeze end # Use is discouraged, this is a potentially slow method and can lead # to even slower algorithms # @return [Array] the right (aka next) siblings def right_siblings return [].freeze unless parent parent.children[sibling_index + 1..-1].freeze end # Common destructuring method. This can be used to normalize # destructuring for different variations of the node. # Some node types override this with their own custom # destructuring method. # # @return [Array] the different parts of the ndde def node_parts to_a end # Calls the given block for each ancestor node from parent to root. # If no block is given, an `Enumerator` is returned. # # @overload each_ancestor # Yield all nodes. # @overload each_ancestor(type) # Yield only nodes matching the type. # @param [Symbol] type a node type # @overload each_ancestor(type_a, type_b, ...) # Yield only nodes matching any of the types. # @param [Symbol] type_a a node type # @param [Symbol] type_b a node type # @yieldparam [Node] node each ancestor node # @return [self] if a block is given # @return [Enumerator] if no block is given def each_ancestor(*types, &block) return to_enum(__method__, *types) unless block_given? visit_ancestors(types, &block) self end # Returns an array of ancestor nodes. # This is a shorthand for `node.each_ancestor.to_a`. # # @return [Array] an array of ancestor nodes def ancestors each_ancestor.to_a end # Calls the given block for each child node. # If no block is given, an `Enumerator` is returned. # # Note that this is different from `node.children.each { |child| ... }` # which yields all children including non-node elements. # # @overload each_child_node # Yield all nodes. # @overload each_child_node(type) # Yield only nodes matching the type. # @param [Symbol] type a node type # @overload each_child_node(type_a, type_b, ...) # Yield only nodes matching any of the types. # @param [Symbol] type_a a node type # @param [Symbol] type_b a node type # @yieldparam [Node] node each child node # @return [self] if a block is given # @return [Enumerator] if no block is given def each_child_node(*types) return to_enum(__method__, *types) unless block_given? children.each do |child| next unless child.is_a?(Node) yield child if types.empty? || types.include?(child.type) end self end # Returns an array of child nodes. # This is a shorthand for `node.each_child_node.to_a`. # # @return [Array] an array of child nodes def child_nodes each_child_node.to_a end # Calls the given block for each descendant node with depth first order. # If no block is given, an `Enumerator` is returned. # # @overload each_descendant # Yield all nodes. # @overload each_descendant(type) # Yield only nodes matching the type. # @param [Symbol] type a node type # @overload each_descendant(type_a, type_b, ...) # Yield only nodes matching any of the types. # @param [Symbol] type_a a node type # @param [Symbol] type_b a node type # @yieldparam [Node] node each descendant node # @return [self] if a block is given # @return [Enumerator] if no block is given def each_descendant(*types, &block) return to_enum(__method__, *types) unless block_given? visit_descendants(types, &block) self end # Returns an array of descendant nodes. # This is a shorthand for `node.each_descendant.to_a`. # # @return [Array] an array of descendant nodes def descendants each_descendant.to_a end # Calls the given block for the receiver and each descendant node in # depth-first order. # If no block is given, an `Enumerator` is returned. # # This method would be useful when you treat the receiver node as the root # of a tree and want to iterate over all nodes in the tree. # # @overload each_node # Yield all nodes. # @overload each_node(type) # Yield only nodes matching the type. # @param [Symbol] type a node type # @overload each_node(type_a, type_b, ...) # Yield only nodes matching any of the types. # @param [Symbol] type_a a node type # @param [Symbol] type_b a node type # @yieldparam [Node] node each node # @return [self] if a block is given # @return [Enumerator] if no block is given def each_node(*types, &block) return to_enum(__method__, *types) unless block_given? yield self if types.empty? || types.include?(type) visit_descendants(types, &block) self end # Note: Some rare nodes may have no source, like `s(:args)` in `foo {}` # @return [String, nil] def source loc.expression&.source end def source_range loc.expression end def first_line loc.line end def last_line loc.last_line end def line_count return 0 unless source_range source_range.last_line - source_range.first_line + 1 end def nonempty_line_count source.lines.grep(/\S/).size end def source_length source_range ? source_range.size : 0 end ## Destructuring def_node_matcher :receiver, <<~PATTERN {(send $_ ...) ({block numblock} (send $_ ...) ...)} PATTERN def_node_matcher :str_content, '(str $_)' def const_name return unless const_type? namespace, name = *self if namespace && !namespace.cbase_type? "#{namespace.const_name}::#{name}" else name.to_s end end def_node_matcher :defined_module0, <<~PATTERN {(class (const $_ $_) ...) (module (const $_ $_) ...) (casgn $_ $_ (send #global_const?({:Class :Module}) :new ...)) (casgn $_ $_ (block (send #global_const?({:Class :Module}) :new ...) ...))} PATTERN private :defined_module0 def defined_module namespace, name = *defined_module0 s(:const, namespace, name) if name end def defined_module_name (const = defined_module) && const.const_name end ## Searching the AST def parent_module_name # what class or module is this method/constant/etc definition in? # returns nil if answer cannot be determined ancestors = each_ancestor(:class, :module, :sclass, :casgn, :block) result = ancestors.map do |ancestor| parent_module_name_part(ancestor) { |full_name| return full_name } end.compact.reverse.join('::') result.empty? ? 'Object' : result end ## Predicates def multiline? line_count > 1 end def single_line? line_count == 1 end def empty_source? source_length.zero? end # Some cops treat the shovel operator as a kind of assignment. def_node_matcher :assignment_or_similar?, <<~PATTERN {assignment? (send _recv :<< ...)} PATTERN def literal? LITERALS.include?(type) end def basic_literal? BASIC_LITERALS.include?(type) end def truthy_literal? TRUTHY_LITERALS.include?(type) end def falsey_literal? FALSEY_LITERALS.include?(type) end def mutable_literal? MUTABLE_LITERALS.include?(type) end def immutable_literal? IMMUTABLE_LITERALS.include?(type) end %i[literal basic_literal].each do |kind| recursive_kind = :"recursive_#{kind}?" kind_filter = :"#{kind}?" define_method(recursive_kind) do case type when :send [*COMPARISON_OPERATORS, :!, :<=>].include?(method_name) && receiver.send(recursive_kind) && arguments.all?(&recursive_kind) when :begin, :pair, *OPERATOR_KEYWORDS, *COMPOSITE_LITERALS children.compact.all?(&recursive_kind) else send(kind_filter) end end end def variable? VARIABLES.include?(type) end def reference? REFERENCES.include?(type) end def equals_asgn? EQUALS_ASSIGNMENTS.include?(type) end def shorthand_asgn? SHORTHAND_ASSIGNMENTS.include?(type) end def assignment? ASSIGNMENTS.include?(type) end def basic_conditional? BASIC_CONDITIONALS.include?(type) end def conditional? CONDITIONALS.include?(type) end def post_condition_loop? POST_CONDITION_LOOP_TYPES.include?(type) end # Note: `loop { }` is a normal method call and thus not a loop keyword. def loop_keyword? LOOP_TYPES.include?(type) end def keyword? return true if special_keyword? || send_type? && prefix_not? return false unless KEYWORDS.include?(type) !OPERATOR_KEYWORDS.include?(type) || loc.operator.is?(type.to_s) end def special_keyword? SPECIAL_KEYWORDS.include?(source) end def operator_keyword? OPERATOR_KEYWORDS.include?(type) end def parenthesized_call? loc.respond_to?(:begin) && loc.begin && loc.begin.is?('(') end def call_type? send_type? || csend_type? end def chained? parent&.call_type? && eql?(parent.receiver) end def argument? parent&.send_type? && parent.arguments.include?(self) end def argument_type? ARGUMENT_TYPES.include?(type) end def boolean_type? true_type? || false_type? end def numeric_type? int_type? || float_type? end def range_type? irange_type? || erange_type? end def guard_clause? node = and_type? || or_type? ? rhs : self node.match_guard_clause? end def_node_matcher :match_guard_clause?, <<~PATTERN [${(send nil? {:raise :fail} ...) return break next} single_line?] PATTERN def_node_matcher :proc?, <<~PATTERN {(block (send nil? :proc) ...) (block (send #global_const?(:Proc) :new) ...) (send #global_const?(:Proc) :new)} PATTERN def_node_matcher :lambda?, '({block numblock} (send nil? :lambda) ...)' def_node_matcher :lambda_or_proc?, '{lambda? proc?}' def_node_matcher :global_const?, '(const {nil? cbase} %1)' def_node_matcher :class_constructor?, <<~PATTERN { (send #global_const?({:Class :Module}) :new ...) (block (send #global_const?({:Class :Module}) :new ...) ...)} PATTERN def_node_matcher :struct_constructor?, <<~PATTERN (block (send #global_const?(:Struct) :new ...) _ $_) PATTERN def_node_matcher :class_definition?, <<~PATTERN {(class _ _ $_) (sclass _ $_) (block (send #global_const?({:Struct :Class}) :new ...) _ $_)} PATTERN def_node_matcher :module_definition?, <<~PATTERN {(module _ $_) (block (send #global_const?(:Module) :new ...) _ $_)} PATTERN # Some expressions are evaluated for their value, some for their side # effects, and some for both # If we know that an expression is useful only for its side effects, that # means we can transform it in ways which preserve the side effects, but # change the return value # So, does the return value of this node matter? If we changed it to # `(...; nil)`, might that affect anything? # # rubocop:disable Metrics/MethodLength def value_used? # Be conservative and return true if we're not sure. return false if parent.nil? case parent.type when :array, :defined?, :dstr, :dsym, :eflipflop, :erange, :float, :hash, :iflipflop, :irange, :not, :pair, :regexp, :str, :sym, :when, :xstr parent.value_used? when :begin, :kwbegin begin_value_used? when :for for_value_used? when :case, :if case_if_value_used? when :while, :until, :while_post, :until_post while_until_value_used? else true end end # rubocop:enable Metrics/MethodLength # Some expressions are evaluated for their value, some for their side # effects, and some for both. # If we know that expressions are useful only for their return values, # and have no side effects, that means we can reorder them, change the # number of times they are evaluated, or replace them with other # expressions which are equivalent in value. # So, is evaluation of this node free of side effects? # def pure? # Be conservative and return false if we're not sure case type when :__FILE__, :__LINE__, :const, :cvar, :defined?, :false, :float, :gvar, :int, :ivar, :lvar, :nil, :str, :sym, :true, :regopt true when :and, :array, :begin, :case, :dstr, :dsym, :eflipflop, :ensure, :erange, :for, :hash, :if, :iflipflop, :irange, :kwbegin, :not, :or, :pair, :regexp, :until, :until_post, :when, :while, :while_post child_nodes.all?(&:pure?) else false end end protected def visit_descendants(types, &block) each_child_node do |child| yield child if types.empty? || types.include?(child.type) child.visit_descendants(types, &block) end end private def visit_ancestors(types) last_node = self while (current_node = last_node.parent) yield current_node if types.empty? || types.include?(current_node.type) last_node = current_node end end def begin_value_used? # the last child node determines the value of the parent sibling_index == parent.children.size - 1 ? parent.value_used? : false end def for_value_used? # `for var in enum; body; end` # (for ) sibling_index == 2 ? parent.value_used? : true end def case_if_value_used? # (case ) # (if ) sibling_index.zero? ? true : parent.value_used? end def while_until_value_used? # (while ) -> always evaluates to `nil` sibling_index.zero? end def parent_module_name_part(node) case node.type when :class, :module, :casgn # TODO: if constant name has cbase (leading ::), then we don't need # to keep traversing up through nested classes/modules node.defined_module_name when :sclass yield parent_module_name_for_sclass(node) else # block parent_module_name_for_block(node) { yield nil } end end def parent_module_name_for_sclass(sclass_node) # TODO: look for constant definition and see if it is nested # inside a class or module subject = sclass_node.children[0] if subject.const_type? "#" elsif subject.self_type? "#" end end def parent_module_name_for_block(ancestor) if ancestor.method?(:class_eval) # `class_eval` with no receiver applies to whatever module or class # we are currently in return unless (receiver = ancestor.receiver) yield unless receiver.const_type? receiver.const_name elsif !new_class_or_module_block?(ancestor) yield end end def_node_matcher :new_class_or_module_block?, <<~PATTERN ^(casgn _ _ (block (send (const _ {:Class :Module}) :new) ...)) PATTERN end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/000077500000000000000000000000001373365104000177105ustar00rootroot00000000000000rubocop-ast-0.6.0/lib/rubocop/ast/node/alias_node.rb000066400000000000000000000012231373365104000223310ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `alias` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `alias` nodes within RuboCop. class AliasNode < Node # Returns the old identifier as specified by the `alias`. # # @return [SymbolNode] the old identifier def old_identifier node_parts[1] end # Returns the new identifier as specified by the `alias`. # # @return [SymbolNode] the new identifier def new_identifier node_parts[0] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/and_node.rb000066400000000000000000000016321373365104000220060ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `until` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `until` nodes within RuboCop. class AndNode < Node include BinaryOperatorNode include PredicateOperatorNode # Returns the alternate operator of the `and` as a string. # Returns `and` for `&&` and vice versa. # # @return [String] the alternate of the `and` operator def alternate_operator logical_operator? ? SEMANTIC_AND : LOGICAL_AND end # Returns the inverse keyword of the `and` node as a string. # Returns `||` for `&&` and `or` for `and`. # # @return [String] the inverse of the `and` operator def inverse_operator logical_operator? ? LOGICAL_OR : SEMANTIC_OR end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/args_node.rb000066400000000000000000000012751373365104000222030ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `args` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `args` nodes within RuboCop. class ArgsNode < Node include CollectionNode # It returns true if arguments are empty and delimiters do not exist. # @example: # # true # def x; end # x { } # -> {} # # # false # def x(); end # def x a; end # x { || } # -> () {} # -> a {} def empty_and_without_delimiters? loc.expression.nil? end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/array_node.rb000066400000000000000000000035451373365104000223670ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `array` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `array` nodes within RuboCop. class ArrayNode < Node PERCENT_LITERAL_TYPES = { string: /^%[wW]/, symbol: /^%[iI]/ }.freeze # Returns an array of all value nodes in the `array` literal. # # @return [Array] an array of value nodes alias values children # @deprecated Use `values.each` (a.k.a. `children.each`) def each_value(&block) return to_enum(__method__) unless block_given? values.each(&block) self end # Checks whether the `array` literal is delimited by square brackets. # # @return [Boolean] whether the array is enclosed in square brackets def square_brackets? loc.begin&.is?('[') end # Checks whether the `array` literal is delimited by percent brackets. # # @overload percent_literal? # Check for any percent literal. # # @overload percent_literal?(type) # Check for percent literal of type `type`. # # @param type [Symbol] an optional percent literal type # # @return [Boolean] whether the array is enclosed in percent brackets def percent_literal?(type = nil) if type loc.begin && loc.begin.source =~ PERCENT_LITERAL_TYPES[type] else loc.begin&.source&.start_with?('%') end end # Checks whether the `array` literal is delimited by either percent or # square brackets # # @return [Boolean] whether the array is enclosed in percent or square # brackets def bracketed? square_brackets? || percent_literal? end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/block_node.rb000066400000000000000000000067701373365104000223460ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `block` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `send` nodes within RuboCop. # # A `block` node is essentially a method send with a block. Parser nests # the `send` node inside the `block` node. class BlockNode < Node include MethodIdentifierPredicates VOID_CONTEXT_METHODS = %i[each tap].freeze # The `send` node associated with this block. # # @return [SendNode] the `send` node associated with the `block` node def send_node node_parts[0] end # The arguments of this block. # # @return [Array] def arguments if numblock_type? [].freeze # Numbered parameters have no block arguments. else node_parts[1] end end # The body of this block. # # @return [Node, nil] the body of the `block` node or `nil` def body node_parts[2] end # The name of the dispatched method as a symbol. # # @return [Symbol] the name of the dispatched method def method_name send_node.method_name end # Checks whether this block takes any arguments. # # @return [Boolean] whether this `block` node takes any arguments def arguments? !arguments.empty? end # Checks whether the `block` literal is delimited by curly braces. # # @return [Boolean] whether the `block` literal is enclosed in braces def braces? loc.end&.is?('}') end # Checks whether the `block` literal is delimited by `do`-`end` keywords. # # @return [Boolean] whether the `block` literal is enclosed in `do`-`end` def keywords? loc.end&.is?('end') end # The delimiters for this `block` literal. # # @return [Array] the delimiters for the `block` literal def delimiters [loc.begin.source, loc.end.source].freeze end # The opening delimiter for this `block` literal. # # @return [String] the opening delimiter for the `block` literal def opening_delimiter delimiters.first end # The closing delimiter for this `block` literal. # # @return [String] the closing delimiter for the `block` literal def closing_delimiter delimiters.last end # Checks whether this is a single line block. This is overridden here # because the general version in `Node` does not work for `block` nodes. # # @return [Boolean] whether the `block` literal is on a single line def single_line? loc.begin.line == loc.end.line end # Checks whether this is a multiline block. This is overridden here # because the general version in `Node` does not work for `block` nodes. # # @return [Boolean] whether the `block` literal is on a several lines def multiline? !single_line? end # Checks whether this `block` literal belongs to a lambda. # # @return [Boolean] whether the `block` literal belongs to a lambda def lambda? send_node.method?(:lambda) end # Checks whether this node body is a void context. # # @return [Boolean] whether the `block` node body is a void context def void_context? VOID_CONTEXT_METHODS.include?(method_name) end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/break_node.rb000066400000000000000000000005421373365104000223270ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `break` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `break` nodes within RuboCop. class BreakNode < Node include ParameterizedNode::WrappedArguments end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/case_match_node.rb000066400000000000000000000026331373365104000233350ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `case_match` nodes. This will be used in place of # a plain node when the builder constructs the AST, making its methods # available to all `case_match` nodes within RuboCop. class CaseMatchNode < Node include ConditionalNode # Returns the keyword of the `case` statement as a string. # # @return [String] the keyword of the `case` statement def keyword 'case' end # @deprecated Use `in_pattern_branches.each` def each_in_pattern(&block) return in_pattern_branches.to_enum(__method__) unless block_given? in_pattern_branches.each(&block) self end # Returns an array of all the when branches in the `case` statement. # # @return [Array] an array of `in_pattern` nodes def in_pattern_branches node_parts[1...-1] end # Returns the else branch of the `case` statement, if any. # # @return [Node] the else branch node of the `case` statement # @return [nil] if the case statement does not have an else branch. def else_branch node_parts[-1] end # Checks whether this case statement has an `else` branch. # # @return [Boolean] whether the `case` statement has an `else` branch def else? !loc.else.nil? end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/case_node.rb000066400000000000000000000033141373365104000221560ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `case` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `case` nodes within RuboCop. class CaseNode < Node include ConditionalNode # Returns the keyword of the `case` statement as a string. # # @return [String] the keyword of the `case` statement def keyword 'case' end # @deprecated Use `when_branches.each` def each_when(&block) return when_branches.to_enum(__method__) unless block_given? when_branches.each(&block) self end # Returns an array of all the when branches in the `case` statement. # # @return [Array] an array of `when` nodes def when_branches node_parts[1...-1] end # Returns an array of all the when branches in the `case` statement. # # @return [Array] an array of the bodies of the when branches # and the else (if any). Note that these bodies could be nil. def branches bodies = when_branches.map(&:body) bodies.push(else_branch) if else? bodies end # Returns the else branch of the `case` statement, if any. # # @return [Node] the else branch node of the `case` statement # @return [nil] if the case statement does not have an else branch. def else_branch node_parts[-1] end # Checks whether this case statement has an `else` branch. # # @return [Boolean] whether the `case` statement has an `else` branch def else? loc.else end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/class_node.rb000066400000000000000000000014071373365104000223510ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `class` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `class` nodes within RuboCop. class ClassNode < Node # The identifer for this `class` node. # # @return [Node] the identifer of the class def identifier node_parts[0] end # The parent class for this `class` node. # # @return [Node, nil] the parent class of the class def parent_class node_parts[1] end # The body of this `class` node. # # @return [Node, nil] the body of the class def body node_parts[2] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/const_node.rb000066400000000000000000000033201373365104000223660ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `const` nodes. class ConstNode < Node # The `send` node associated with this block. # # @return [Node, nil] the node associated with the scope (e.g. cbase, const, ...) def namespace children[0] end # @return [Symbol] the demodulized name of the constant: "::Foo::Bar" => :Bar def short_name children[1] end # The body of this block. # # @return [Boolean] if the constant is a Module / Class, according to the standard convention. # Note: some classes might have uppercase in which case this method # returns false def module_name? short_name.match?(/[[:lower:]]/) end alias class_name? module_name? # @return [Boolean] if the constant starts with `::` (aka s(:cbase)) def absolute? return false unless namespace each_path.first.cbase_type? end # @return [Boolean] if the constant does not start with `::` (aka s(:cbase)) def relative? !absolute? end # Yield nodes for the namespace # # For `::Foo::Bar::BAZ` => yields: # s(:cbase), then # s(:const, :Foo), then # s(:const, s(:const, :Foo), :Bar) def each_path(&block) return to_enum(__method__) unless block_given? descendants = [] last = self loop do last = last.children.first break if last.nil? descendants << last break unless last.const_type? end descendants.reverse_each(&block) self end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/def_node.rb000066400000000000000000000036701373365104000220060ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `def` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `def` nodes within RuboCop. class DefNode < Node include ParameterizedNode include MethodIdentifierPredicates # Checks whether this node body is a void context. # # @return [Boolean] whether the `def` node body is a void context def void_context? method?(:initialize) || assignment_method? end # Checks whether this method definition node forwards its arguments # as per the feature added in Ruby 2.7. # # @note This is written in a way that may support lead arguments # which are rumored to be added in a later version of Ruby. # # @return [Boolean] whether the `def` node uses argument forwarding def argument_forwarding? arguments.any?(&:forward_args_type?) || arguments.any?(&:forward_arg_type?) end # The name of the defined method as a symbol. # # @return [Symbol] the name of the defined method def method_name children[-3] end # An array containing the arguments of the method definition. # # @return [Array] the arguments of the method definition def arguments children[-2] end # The body of the method definition. # # @note this can be either a `begin` node, if the method body contains # multiple expressions, or any other node, if it contains a single # expression. # # @return [Node] the body of the method definition def body children[-1] end # The receiver of the method definition, if any. # # @return [Node, nil] the receiver of the method definition, or `nil`. def receiver children[-4] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/defined_node.rb000066400000000000000000000007251373365104000226440ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `defined?` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `send` nodes within RuboCop. class DefinedNode < Node include ParameterizedNode include MethodDispatchNode def node_parts [nil, :defined?, *to_a] end alias arguments children end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/ensure_node.rb000066400000000000000000000007211373365104000225430ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `ensure` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `ensure` nodes within RuboCop. class EnsureNode < Node # Returns the body of the `ensure` clause. # # @return [Node, nil] The body of the `ensure`. def body node_parts[1] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/float_node.rb000066400000000000000000000005511373365104000223500ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `float` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available to # all `float` nodes within RuboCop. class FloatNode < Node include BasicLiteralNode include NumericNode end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/for_node.rb000066400000000000000000000026051373365104000220330ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `for` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `for` nodes within RuboCop. class ForNode < Node # Returns the keyword of the `for` statement as a string. # # @return [String] the keyword of the `until` statement def keyword 'for' end # Checks whether the `for` node has a `do` keyword. # # @return [Boolean] whether the `for` node has a `do` keyword def do? loc.begin&.is?('do') end # Checks whether this node body is a void context. # Always `true` for `for`. # # @return [true] whether the `for` node body is a void context def void_context? true end # Returns the iteration variable of the `for` loop. # # @return [Node] The iteration variable of the `for` loop def variable node_parts[0] end # Returns the collection the `for` loop is iterating over. # # @return [Node] The collection the `for` loop is iterating over def collection node_parts[1] end # Returns the body of the `for` loop. # # @return [Node, nil] The body of the `for` loop. def body node_parts[2] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/forward_args_node.rb000066400000000000000000000016651373365104000237320ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `forward-args` nodes. This will be used in place # of a plain node when the builder constructs the AST, making its methods # available to all `forward-args` nodes within RuboCop. # # Not used with modern emitters: # # $ ruby-parse -e "def foo(...); end" # (def :foo # (args # (forward-arg)) nil) # $ ruby-parse --legacy -e "->(foo) { bar }" # (def :foo # (forward-args) nil) # # Note the extra 's' with legacy form. # # The main RuboCop runs in legacy mode; this node is only used # if user `AST::Builder.modernize` or `AST::Builder.emit_lambda=true` class ForwardArgsNode < Node include CollectionNode # Node wraps itself in an array to be compatible with other # enumerable argument types. def to_a [self] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/hash_node.rb000066400000000000000000000070771373365104000222000ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `hash` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `hash` nodes within RuboCop. class HashNode < Node # Returns an array of all the key value pairs in the `hash` literal. # # @note this may be different from children as `kwsplat` nodes are # ignored. # # @return [Array] an array of `pair` nodes def pairs each_pair.to_a end # Checks whether the `hash` node contains any `pair`- or `kwsplat` nodes. # # @return[Boolean] whether the `hash` is empty def empty? children.empty? end # Calls the given block for each `pair` node in the `hash` literal. # If no block is given, an `Enumerator` is returned. # # @note `kwsplat` nodes are ignored. # # @return [self] if a block is given # @return [Enumerator] if no block is given def each_pair return each_child_node(:pair).to_enum unless block_given? each_child_node(:pair) do |pair| yield(*pair) end self end # Returns an array of all the keys in the `hash` literal. # # @note `kwsplat` nodes are ignored. # # @return [Array] an array of keys in the `hash` literal def keys each_key.to_a end # Calls the given block for each `key` node in the `hash` literal. # If no block is given, an `Enumerator` is returned. # # @note `kwsplat` nodes are ignored. # # @return [self] if a block is given # @return [Enumerator] if no block is given def each_key(&block) return pairs.map(&:key).to_enum unless block_given? pairs.map(&:key).each(&block) self end # Returns an array of all the values in the `hash` literal. # # @note `kwsplat` nodes are ignored. # # @return [Array] an array of values in the `hash` literal def values each_pair.map(&:value) end # Calls the given block for each `value` node in the `hash` literal. # If no block is given, an `Enumerator` is returned. # # @note `kwsplat` nodes are ignored. # # @return [self] if a block is given # @return [Enumerator] if no block is given def each_value(&block) return pairs.map(&:value).to_enum unless block_given? pairs.map(&:value).each(&block) self end # Checks whether any of the key value pairs in the `hash` literal are on # the same line. # # @note A multiline `pair` is considered to be on the same line if it # shares any of its lines with another `pair` # # @note `kwsplat` nodes are ignored. # # @return [Boolean] whether any `pair` nodes are on the same line def pairs_on_same_line? pairs.each_cons(2).any? { |first, second| first.same_line?(second) } end # Checks whether this `hash` uses a mix of hash rocket and colon # delimiters for its pairs. # # @note `kwsplat` nodes are ignored. # # @return [Boolean] whether the `hash` uses mixed delimiters def mixed_delimiters? pairs.map(&:delimiter).uniq.size > 1 end # Checks whether the `hash` literal is delimited by curly braces. # # @return [Boolean] whether the `hash` literal is enclosed in braces def braces? loc.end&.is?('}') end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/if_node.rb000066400000000000000000000121321373365104000216370ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `if` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `if` nodes within RuboCop. class IfNode < Node include ConditionalNode include ModifierNode # Checks whether this node is an `if` statement. (This is not true of # ternary operators and `unless` statements.) # # @return [Boolean] whether the node is an `if` statement def if? keyword == 'if' end # Checks whether this node is an `unless` statement. (This is not true # of ternary operators and `if` statements.) # # @return [Boolean] whether the node is an `unless` statement def unless? keyword == 'unless' end # Checks whether the `if` is an `elsif`. Parser handles these by nesting # `if` nodes in the `else` branch. # # @return [Boolean] whether the node is an `elsif` def elsif? keyword == 'elsif' end # Checks whether the `if` node has an `else` clause. # # @note This returns `true` for nodes containing an `elsif` clause. # This is legacy behavior, and many cops rely on it. # # @return [Boolean] whether the node has an `else` clause def else? loc.respond_to?(:else) && loc.else end # Checks whether the `if` node is a ternary operator. # # @return [Boolean] whether the `if` node is a ternary operator def ternary? loc.respond_to?(:question) end # Returns the keyword of the `if` statement as a string. Returns an empty # string for ternary operators. # # @return [String] the keyword of the `if` statement def keyword ternary? ? '' : loc.keyword.source end # Returns the inverse keyword of the `if` node as a string. Returns `if` # for `unless` nodes and vice versa. Returns an empty string for ternary # operators. # # @return [String] the inverse keyword of the `if` statement def inverse_keyword case keyword when 'if' then 'unless' when 'unless' then 'if' else '' end end # Checks whether the `if` node is in a modifier form, i.e. a condition # trailing behind an expression. Only `if` and `unless` nodes without # other branches can be modifiers. # # @return [Boolean] whether the `if` node is a modifier def modifier_form? (if? || unless?) && super end # Chacks whether the `if` node has nested `if` nodes in any of its # branches. # # @note This performs a shallow search. # # @return [Boolean] whether the `if` node contains nested conditionals def nested_conditional? node_parts[1..2].compact.each do |branch| branch.each_node(:if) do |nested| return true unless nested.elsif? end end false end # Checks whether the `if` node has at least one `elsif` branch. Returns # true if this `if` node itself is an `elsif`. # # @return [Boolean] whether the `if` node has at least one `elsif` branch def elsif_conditional? else_branch&.if_type? && else_branch&.elsif? end # Returns the branch of the `if` node that gets evaluated when its # condition is truthy. # # @note This is normalized for `unless` nodes. # # @return [Node] the truthy branch node of the `if` node # @return [nil] if the truthy branch is empty def if_branch node_parts[1] end # Returns the branch of the `if` node that gets evaluated when its # condition is falsey. # # @note This is normalized for `unless` nodes. # # @return [Node] the falsey branch node of the `if` node # @return [nil] when there is no else branch def else_branch node_parts[2] end # Custom destructuring method. This is used to normalize the branches # for `if` and `unless` nodes, to aid comparisons and conversions. # # @return [Array] the different parts of the `if` statement def node_parts if unless? condition, false_branch, true_branch = *self else condition, true_branch, false_branch = *self end [condition, true_branch, false_branch] end # Returns an array of all the branches in the conditional statement. # # @return [Array] an array of branch nodes def branches branches = [if_branch] return branches unless else? other_branches = if elsif_conditional? else_branch.branches else [else_branch] end branches.concat(other_branches) end # @deprecated Use `branches.each` def each_branch(&block) return branches.to_enum(__method__) unless block_given? branches.each(&block) end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/index_node.rb000066400000000000000000000021401373365104000223460ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Used for modern support only! # Not as thoroughly tested as legacy equivalent # # $ ruby-parse -e "foo[:bar]" # (index # (send nil :foo) # (sym :bar)) # $ ruby-parse --legacy -e "foo[:bar]" # (send # (send nil :foo) :[] # (sym :bar)) # # The main RuboCop runs in legacy mode; this node is only used # if user `AST::Builder.modernize` or `AST::Builder.emit_index=true` class IndexNode < Node include ParameterizedNode::RestArguments include MethodDispatchNode # For similarity with legacy mode def attribute_accessor? false end # For similarity with legacy mode def assignment_method? false end # For similarity with legacy mode def method_name :[] end private # An array containing the arguments of the dispatched method. # # @return [Array] the arguments of the dispatched method def first_argument_index 1 end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/indexasgn_node.rb000066400000000000000000000022411373365104000232210ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Used for modern support only! # Not as thoroughly tested as legacy equivalent # # $ ruby-parse -e "foo[:bar] = :baz" # (indexasgn # (send nil :foo) # (sym :bar) # (sym :baz)) # $ ruby-parse --legacy -e "foo[:bar] = :baz" # (send # (send nil :foo) :[]= # (sym :bar) # (sym :baz)) # # The main RuboCop runs in legacy mode; this node is only used # if user `AST::Builder.modernize` or `AST::Builder.emit_index=true` class IndexasgnNode < Node include ParameterizedNode::RestArguments include MethodDispatchNode # For similarity with legacy mode def attribute_accessor? false end # For similarity with legacy mode def assignment_method? true end # For similarity with legacy mode def method_name :[]= end private # An array containing the arguments of the dispatched method. # # @return [Array] the arguments of the dispatched method def first_argument_index 1 end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/int_node.rb000066400000000000000000000005431373365104000220360ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `int` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available to # all `int` nodes within RuboCop. class IntNode < Node include BasicLiteralNode include NumericNode end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/keyword_splat_node.rb000066400000000000000000000022521373365104000241320ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `kwsplat` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `kwsplat` nodes within RuboCop. class KeywordSplatNode < Node include HashElementNode DOUBLE_SPLAT = '**' # This is used for duck typing with `pair` nodes which also appear as # `hash` elements. # # @return [false] def hash_rocket? false end # This is used for duck typing with `pair` nodes which also appear as # `hash` elements. # # @return [false] def colon? false end # Returns the operator for the `kwsplat` as a string. # # @return [String] the double splat operator def operator DOUBLE_SPLAT end # Custom destructuring method. This is used to normalize the branches # for `pair` and `kwsplat` nodes, to add duck typing to `hash` elements. # # @return [Array] the different parts of the `kwsplat` def node_parts [self, self] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/lambda_node.rb000066400000000000000000000025321373365104000224640ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Used for modern support only: # Not as thoroughly tested as legacy equivalent # # $ ruby-parse -e "->(foo) { bar }" # (block # (lambda) # (args # (arg :foo)) # (send nil :bar)) # $ ruby-parse --legacy -e "->(foo) { bar }" # (block # (send nil :lambda) # (args # (arg :foo)) # (send nil :bar)) # # The main RuboCop runs in legacy mode; this node is only used # if user `AST::Builder.modernize` or `AST::Builder.emit_lambda=true` class LambdaNode < Node include ParameterizedNode::RestArguments include MethodDispatchNode # For similarity with legacy mode def lambda? true end # For similarity with legacy mode def lambda_literal? true end # For similarity with legacy mode def attribute_accessor? false end # For similarity with legacy mode def assignment_method? false end # For similarity with legacy mode def receiver nil end # For similarity with legacy mode def method_name :lambda end private # For similarity with legacy mode def first_argument_index 2 end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/000077500000000000000000000000001373365104000210345ustar00rootroot00000000000000rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/basic_literal_node.rb000066400000000000000000000005341373365104000251650ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for primitive literal nodes: `sym`, `str`, # `int`, `float`, ... module BasicLiteralNode # Returns the value of the literal. # # @return [mixed] the value of the literal def value node_parts[0] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/binary_operator_node.rb000066400000000000000000000023011373365104000255610ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for nodes that are binary operations: # `or`, `and` ... module BinaryOperatorNode # Returns the left hand side node of the binary operation. # # @return [Node] the left hand side of the binary operation def lhs node_parts[0] end # Returns the right hand side node of the binary operation. # # @return [Node] the right hand side of the binary operation def rhs node_parts[1] end # Returns all of the conditions, including nested conditions, # of the binary operation. # # @return [Array] the left and right hand side of the binary # operation and the let and right hand side of any nested binary # operators def conditions lhs, rhs = *self lhs = lhs.children.first if lhs.begin_type? rhs = rhs.children.first if rhs.begin_type? [lhs, rhs].each_with_object([]) do |side, collection| if side.operator_keyword? collection.concat(side.conditions) else collection << side end end end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/collection_node.rb000066400000000000000000000005221373365104000245200ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A mixin that helps give collection nodes array polymorphism. module CollectionNode extend Forwardable ARRAY_METHODS = (Array.instance_methods - Object.instance_methods - [:to_a]).freeze def_delegators :to_a, *ARRAY_METHODS end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/conditional_node.rb000066400000000000000000000026471373365104000247020ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for nodes that have conditions: # `if`, `while`, `until`, `case`. # This currently doesn't include `when` nodes, because they have multiple # conditions, and need to be checked for that. module ConditionalNode # Checks whether the condition of the node is written on a single line. # # @return [Boolean] whether the condition is on a single line def single_line_condition? loc.keyword.line == condition.source_range.line end # Checks whether the condition of the node is written on more than # one line. # # @return [Boolean] whether the condition is on more than one line def multiline_condition? !single_line_condition? end # Returns the condition of the node. This works together with each node's # custom destructuring method to select the correct part of the node. # # @return [Node, nil] the condition of the node def condition node_parts[0] end # Returns the body associated with the condition. This works together with # each node's custom destructuring method to select the correct part of # the node. # # @note For `if` nodes, this is the truthy branch. # # @return [Node, nil] the body of the node def body node_parts[1] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/hash_element_node.rb000066400000000000000000000071501373365104000250250ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for nodes that can be used as hash elements: # `pair`, `kwsplat` module HashElementNode # Returns the key of this `hash` element. # # @note For keyword splats, this returns the whole node # # @return [Node] the key of the hash element def key node_parts[0] end # Returns the value of this `hash` element. # # @note For keyword splats, this returns the whole node # # @return [Node] the value of the hash element def value node_parts[1] end # Checks whether this `hash` element is on the same line as `other`. # # @note A multiline element is considered to be on the same line if it # shares any of its lines with `other` # # @return [Boolean] whether this element is on the same line as `other` def same_line?(other) loc.last_line == other.loc.line || loc.line == other.loc.last_line end # Returns the delta between this pair's key and the argument pair's. # # @note Keys on the same line always return a delta of 0 # @note Keyword splats always return a delta of 0 for right alignment # # @param [Symbol] alignment whether to check the left or right side # @return [Integer] the delta between the two keys def key_delta(other, alignment = :left) HashElementDelta.new(self, other).key_delta(alignment) end # Returns the delta between this element's value and the argument's. # # @note Keyword splats always return a delta of 0 # # @return [Integer] the delta between the two values def value_delta(other) HashElementDelta.new(self, other).value_delta end # Returns the delta between this element's delimiter and the argument's. # # @note Pairs with different delimiter styles return a delta of 0 # # @return [Integer] the delta between the two delimiters def delimiter_delta(other) HashElementDelta.new(self, other).delimiter_delta end # A helper class for comparing the positions of different parts of a # `pair` node. class HashElementDelta def initialize(first, second) @first = first @second = second raise ArgumentError unless valid_argument_types? end def key_delta(alignment = :left) return 0 if first.same_line?(second) return 0 if keyword_splat? && alignment == :right delta(first.key.loc, second.key.loc, alignment) end def value_delta return 0 if first.same_line?(second) return 0 if keyword_splat? delta(first.value.loc, second.value.loc) end def delimiter_delta return 0 if first.same_line?(second) return 0 if first.delimiter != second.delimiter delta(first.loc.operator, second.loc.operator) end private attr_reader :first, :second def valid_argument_types? [first, second].all? do |argument| argument.pair_type? || argument.kwsplat_type? end end def delta(first, second, alignment = :left) case alignment when :left first.column - second.column when :right first.last_column - second.last_column else 0 end end def keyword_splat? [first, second].any?(&:kwsplat_type?) end end private_constant :HashElementDelta end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/method_dispatch_node.rb000066400000000000000000000204271373365104000255320ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for nodes that are a kind of method dispatch: # `send`, `csend`, `super`, `zsuper`, `yield`, `defined?`, # and (modern only): `index`, `indexasgn`, `lambda` module MethodDispatchNode extend NodePattern::Macros include MethodIdentifierPredicates ARITHMETIC_OPERATORS = %i[+ - * / % **].freeze SPECIAL_MODIFIERS = %w[private protected].freeze # The receiving node of the method dispatch. # # @return [Node, nil] the receiver of the dispatched method or `nil` def receiver node_parts[0] end # The name of the dispatched method as a symbol. # # @return [Symbol] the name of the dispatched method def method_name node_parts[1] end # The `block` node associated with this method dispatch, if any. # # @return [BlockNode, nil] the `block` node associated with this method # call or `nil` def block_node parent if block_literal? end # Checks whether the dispatched method is a macro method. A macro method # is defined as a method that sits in a class, module, or block body and # has an implicit receiver. # # @note This does not include DSLs that use nested blocks, like RSpec # # @return [Boolean] whether the dispatched method is a macro method def macro? !receiver && in_macro_scope? end # Checks whether the dispatched method is an access modifier. # # @return [Boolean] whether the dispatched method is an access modifier def access_modifier? bare_access_modifier? || non_bare_access_modifier? end # Checks whether the dispatched method is a bare access modifier that # affects all methods defined after the macro. # # @return [Boolean] whether the dispatched method is a bare # access modifier def bare_access_modifier? macro? && bare_access_modifier_declaration? end # Checks whether the dispatched method is a non-bare access modifier that # affects only the method it receives. # # @return [Boolean] whether the dispatched method is a non-bare # access modifier def non_bare_access_modifier? macro? && non_bare_access_modifier_declaration? end # Checks whether the dispatched method is a bare `private` or `protected` # access modifier that affects all methods defined after the macro. # # @return [Boolean] whether the dispatched method is a bare # `private` or `protected` access modifier def special_modifier? bare_access_modifier? && SPECIAL_MODIFIERS.include?(source) end # Checks whether the name of the dispatched method matches the argument # and has an implicit receiver. # # @param [Symbol, String] name the method name to check for # @return [Boolean] whether the method name matches the argument def command?(name) !receiver && method?(name) end # Checks whether the dispatched method is a setter method. # # @return [Boolean] whether the dispatched method is a setter def setter_method? loc.respond_to?(:operator) && loc.operator end alias assignment? setter_method? # Checks whether the dispatched method uses a dot to connect the # receiver and the method name. # # This is useful for comparison operators, which can be called either # with or without a dot, i.e. `foo == bar` or `foo.== bar`. # # @return [Boolean] whether the method was called with a connecting dot def dot? loc.respond_to?(:dot) && loc.dot && loc.dot.is?('.') end # Checks whether the dispatched method uses a double colon to connect the # receiver and the method name. # # @return [Boolean] whether the method was called with a connecting dot def double_colon? loc.respond_to?(:dot) && loc.dot && loc.dot.is?('::') end # Checks whether the dispatched method uses a safe navigation operator to # connect the receiver and the method name. # # @return [Boolean] whether the method was called with a connecting dot def safe_navigation? loc.respond_to?(:dot) && loc.dot && loc.dot.is?('&.') end # Checks whether the *explicit* receiver of this method dispatch is # `self`. # # @return [Boolean] whether the receiver of this method dispatch is `self` def self_receiver? receiver&.self_type? end # Checks whether the *explicit* receiver of this method dispatch is a # `const` node. # # @return [Boolean] whether the receiver of this method dispatch # is a `const` node def const_receiver? receiver&.const_type? end # Checks whether the method dispatch is the implicit form of `#call`, # e.g. `foo.(bar)`. # # @return [Boolean] whether the method is the implicit form of `#call` def implicit_call? method?(:call) && !loc.selector end # Whether this method dispatch has an explicit block. # # @return [Boolean] whether the dispatched method has a block def block_literal? parent&.block_type? && eql?(parent.send_node) end # Checks whether this node is an arithmetic operation # # @return [Boolean] whether the dispatched method is an arithmetic # operation def arithmetic_operation? ARITHMETIC_OPERATORS.include?(method_name) end # Checks if this node is part of a chain of `def` modifiers. # # @example # # private def foo; end # # @return [Boolean] whether the dispatched method is a `def` modifier def def_modifier? send_type? && [self, *each_descendant(:send)].any?(&:adjacent_def_modifier?) end # Checks whether this is a lambda. Some versions of parser parses # non-literal lambdas as a method send. # # @return [Boolean] whether this method is a lambda def lambda? block_literal? && command?(:lambda) end # Checks whether this is a lambda literal (stabby lambda.) # # @example # # -> (foo) { bar } # # @return [Boolean] whether this method is a lambda literal def lambda_literal? block_literal? && loc.expression && loc.expression.source == '->' end # Checks whether this is a unary operation. # # @example # # -foo # # @return [Boolean] whether this method is a unary operation def unary_operation? return false unless loc.selector operator_method? && loc.expression.begin_pos == loc.selector.begin_pos end # Checks whether this is a binary operation. # # @example # # foo + bar # # @return [Bookean] whether this method is a binary operation def binary_operation? return false unless loc.selector operator_method? && loc.expression.begin_pos != loc.selector.begin_pos end private def_node_matcher :in_macro_scope?, <<~PATTERN { root? # Either a root node, ^{ # or the parent is... sclass class module class_constructor? # a class-like node [ { # or some "wrapper" kwbegin begin block (if _condition <%0 _>) # note: we're excluding the condition of `if` nodes } #in_macro_scope? # that is itself in a macro scope ] } } PATTERN def_node_matcher :adjacent_def_modifier?, <<~PATTERN (send nil? _ ({def defs} ...)) PATTERN def_node_matcher :bare_access_modifier_declaration?, <<~PATTERN (send nil? {:public :protected :private :module_function}) PATTERN def_node_matcher :non_bare_access_modifier_declaration?, <<~PATTERN (send nil? {:public :protected :private :module_function} _) PATTERN end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb000066400000000000000000000173771373365104000272650ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common predicates for nodes that reference method identifiers: # `send`, `csend`, `def`, `defs`, `super`, `zsuper` # # @note this mixin expects `#method_name` and `#receiver` to be implemented module MethodIdentifierPredicates # rubocop:disable Metrics/ModuleLength ENUMERATOR_METHODS = %i[collect collect_concat detect downto each find find_all find_index inject loop map! map reduce reject reject! reverse_each select select! times upto].to_set.freeze ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).to_set.freeze # http://phrogz.net/programmingruby/language.html#table_18.4 OPERATOR_METHODS = %i[| ^ & <=> == === =~ > >= < <= << >> + - * / % ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].to_set.freeze NONMUTATING_BINARY_OPERATOR_METHODS = %i[* / % + - == === != < > <= >= <=>].to_set.freeze NONMUTATING_UNARY_OPERATOR_METHODS = %i[+@ -@ ~ !].to_set.freeze NONMUTATING_OPERATOR_METHODS = (NONMUTATING_BINARY_OPERATOR_METHODS + NONMUTATING_UNARY_OPERATOR_METHODS).freeze NONMUTATING_ARRAY_METHODS = %i[ all? any? assoc at bsearch bsearch_index collect combination compact count cycle deconstruct difference dig drop drop_while each each_index empty? eql? fetch filter find_index first flatten hash include? index inspect intersection join last length map max min minmax none? one? pack permutation product rassoc reject repeated_combination repeated_permutation reverse reverse_each rindex rotate sample select shuffle size slice sort sum take take_while to_a to_ary to_h to_s transpose union uniq values_at zip | ].to_set.freeze NONMUTATING_HASH_METHODS = %i[ any? assoc compact dig each each_key each_pair each_value empty? eql? fetch fetch_values filter flatten has_key? has_value? hash include? inspect invert key key? keys? length member? merge rassoc rehash reject select size slice to_a to_h to_hash to_proc to_s transform_keys transform_values value? values values_at ].to_set.freeze NONMUTATING_STRING_METHODS = %i[ ascii_only? b bytes bytesize byteslice capitalize casecmp casecmp? center chars chomp chop chr codepoints count crypt delete delete_prefix delete_suffix downcase dump each_byte each_char each_codepoint each_grapheme_cluster each_line empty? encode encoding end_with? eql? getbyte grapheme_clusters gsub hash hex include index inspect intern length lines ljust lstrip match match? next oct ord partition reverse rindex rjust rpartition rstrip scan scrub size slice squeeze start_with? strip sub succ sum swapcase to_a to_c to_f to_i to_r to_s to_str to_sym tr tr_s unicode_normalize unicode_normalized? unpack unpack1 upcase upto valid_encoding? ].to_set.freeze # Checks whether the method name matches the argument. # # @param [Symbol, String] name the method name to check for # @return [Boolean] whether the method name matches the argument def method?(name) method_name == name.to_sym end # Checks whether the method is an operator method. # # @return [Boolean] whether the method is an operator def operator_method? OPERATOR_METHODS.include?(method_name) end # Checks whether the method is a nonmutating binary operator method. # # @return [Boolean] whether the method is a nonmutating binary operator method def nonmutating_binary_operator_method? NONMUTATING_BINARY_OPERATOR_METHODS.include?(method_name) end # Checks whether the method is a nonmutating unary operator method. # # @return [Boolean] whether the method is a nonmutating unary operator method def nonmutating_unary_operator_method? NONMUTATING_UNARY_OPERATOR_METHODS.include?(method_name) end # Checks whether the method is a nonmutating operator method. # # @return [Boolean] whether the method is a nonmutating operator method def nonmutating_operator_method? NONMUTATING_OPERATOR_METHODS.include?(method_name) end # Checks whether the method is a nonmutating Array method. # # @return [Boolean] whether the method is a nonmutating Array method def nonmutating_array_method? NONMUTATING_ARRAY_METHODS.include?(method_name) end # Checks whether the method is a nonmutating Hash method. # # @return [Boolean] whether the method is a nonmutating Hash method def nonmutating_hash_method? NONMUTATING_HASH_METHODS.include?(method_name) end # Checks whether the method is a nonmutating String method. # # @return [Boolean] whether the method is a nonmutating String method def nonmutating_string_method? NONMUTATING_STRING_METHODS.include?(method_name) end # Checks whether the method is a comparison method. # # @return [Boolean] whether the method is a comparison def comparison_method? Node::COMPARISON_OPERATORS.include?(method_name) end # Checks whether the method is an assignment method. # # @return [Boolean] whether the method is an assignment def assignment_method? !comparison_method? && method_name.to_s.end_with?('=') end # Checks whether the method is an enumerator method. # # @return [Boolean] whether the method is an enumerator def enumerator_method? ENUMERATOR_METHODS.include?(method_name) || method_name.to_s.start_with?('each_') end # Checks whether the method is an Enumerable method. # # @return [Boolean] whether the method is an Enumerable method def enumerable_method? ENUMERABLE_METHODS.include?(method_name) end # Checks whether the method is a predicate method. # # @return [Boolean] whether the method is a predicate method def predicate_method? method_name.to_s.end_with?('?') end # Checks whether the method is a bang method. # # @return [Boolean] whether the method is a bang method def bang_method? method_name.to_s.end_with?('!') end # Checks whether the method is a camel case method, # e.g. `Integer()`. # # @return [Boolean] whether the method is a camel case method def camel_case_method? method_name.to_s =~ /\A[A-Z]/ end # Checks whether the *explicit* receiver of this node is `self`. # # @return [Boolean] whether the receiver of this node is `self` def self_receiver? receiver&.self_type? end # Checks whether the *explicit* receiver of node is a `const` node. # # @return [Boolean] whether the receiver of this node is a `const` node def const_receiver? receiver&.const_type? end # Checks whether this is a negation method, i.e. `!` or keyword `not`. # # @return [Boolean] whether this method is a negation method def negation_method? receiver && method_name == :! end # Checks whether this is a prefix not method, e.g. `not foo`. # # @return [Boolean] whether this method is a prefix not def prefix_not? negation_method? && loc.selector.is?('not') end # Checks whether this is a prefix bang method, e.g. `!foo`. # # @return [Boolean] whether this method is a prefix bang def prefix_bang? negation_method? && loc.selector.is?('!') end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/modifier_node.rb000066400000000000000000000006561373365104000241730ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for nodes that can be used as modifiers: # `if`, `while`, `until` module ModifierNode # Checks whether the node is in a modifier form, i.e. a condition # trailing behind an expression. # # @return [Boolean] whether the node is a modifier def modifier_form? loc.end.nil? end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/numeric_node.rb000066400000000000000000000006641373365104000240360ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for primitive numeric nodes: `int`, `float`, ... module NumericNode SIGN_REGEX = /\A[+-]/.freeze # Checks whether this is literal has a sign. # # @example # # +42 # # @return [Boolean] whether this literal has a sign. def sign? source.match(SIGN_REGEX) end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/parameterized_node.rb000066400000000000000000000072121373365104000252240ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Requires implementing `arguments`. # # Common functionality for nodes that are parameterized: # `send`, `super`, `zsuper`, `def`, `defs` # and (modern only): `index`, `indexasgn`, `lambda` module ParameterizedNode # Checks whether this node's arguments are wrapped in parentheses. # # @return [Boolean] whether this node's arguments are # wrapped in parentheses def parenthesized? loc.end&.is?(')') end # A shorthand for getting the first argument of the node. # Equivalent to `arguments.first`. # # @return [Node, nil] the first argument of the node, # or `nil` if there are no arguments def first_argument arguments[0] end # A shorthand for getting the last argument of the node. # Equivalent to `arguments.last`. # # @return [Node, nil] the last argument of the node, # or `nil` if there are no arguments def last_argument arguments[-1] end # Checks whether this node has any arguments. # # @return [Boolean] whether this node has any arguments def arguments? !arguments.empty? end # Checks whether any argument of the node is a splat # argument, i.e. `*splat`. # # @return [Boolean] whether the node is a splat argument def splat_argument? arguments? && (arguments.any?(&:splat_type?) || arguments.any?(&:restarg_type?)) end alias rest_argument? splat_argument? # Whether the last argument of the node is a block pass, # i.e. `&block`. # # @return [Boolean] whether the last argument of the node is a block pass def block_argument? arguments? && (last_argument.block_pass_type? || last_argument.blockarg_type?) end # A specialized `ParameterizedNode` for node that have a single child # containing either `nil`, an argument, or a `begin` node with all the # arguments module WrappedArguments include ParameterizedNode # @return [Array] The arguments of the node. def arguments first = children.first if first&.begin_type? first.children else children end end end # A specialized `ParameterizedNode`. # Requires implementing `first_argument_index` # Implements `arguments` as `children[first_argument_index..-1]` # and optimizes other calls module RestArguments include ParameterizedNode # @return [Array] arguments, if any def arguments children[first_argument_index..-1].freeze end # A shorthand for getting the first argument of the node. # Equivalent to `arguments.first`. # # @return [Node, nil] the first argument of the node, # or `nil` if there are no arguments def first_argument children[first_argument_index] end # A shorthand for getting the last argument of the node. # Equivalent to `arguments.last`. # # @return [Node, nil] the last argument of the node, # or `nil` if there are no arguments def last_argument children[-1] if arguments? end # Checks whether this node has any arguments. # # @return [Boolean] whether this node has any arguments def arguments? children.size > first_argument_index end end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/mixin/predicate_operator_node.rb000066400000000000000000000015601373365104000262430ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for nodes that are predicates: # `or`, `and` ... module PredicateOperatorNode LOGICAL_AND = '&&' SEMANTIC_AND = 'and' LOGICAL_OR = '||' SEMANTIC_OR = 'or' # Returns the operator as a string. # # @return [String] the operator def operator loc.operator.source end # Checks whether this is a logical operator. # # @return [Boolean] whether this is a logical operator def logical_operator? operator == LOGICAL_AND || operator == LOGICAL_OR end # Checks whether this is a semantic operator. # # @return [Boolean] whether this is a semantic operator def semantic_operator? operator == SEMANTIC_AND || operator == SEMANTIC_OR end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/module_node.rb000066400000000000000000000011441373365104000225270ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `module` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `module` nodes within RuboCop. class ModuleNode < Node # The identifer for this `module` node. # # @return [Node] the identifer of the module def identifier node_parts[0] end # The body of this `module` node. # # @return [Node, nil] the body of the module def body node_parts[1] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/next_node.rb000066400000000000000000000005371373365104000222250ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `next` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `next` nodes within RuboCop. class NextNode < Node include ParameterizedNode::WrappedArguments end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/or_node.rb000066400000000000000000000016161373365104000216660ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `or` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `or` nodes within RuboCop. class OrNode < Node include BinaryOperatorNode include PredicateOperatorNode # Returns the alternate operator of the `or` as a string. # Returns `or` for `||` and vice versa. # # @return [String] the alternate of the `or` operator def alternate_operator logical_operator? ? SEMANTIC_OR : LOGICAL_OR end # Returns the inverse keyword of the `or` node as a string. # Returns `and` for `or` and `&&` for `||`. # # @return [String] the inverse of the `or` operator def inverse_operator logical_operator? ? LOGICAL_AND : SEMANTIC_AND end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/pair_node.rb000066400000000000000000000037351373365104000222050ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `pair` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `pair` nodes within RuboCop. class PairNode < Node include HashElementNode HASH_ROCKET = '=>' SPACED_HASH_ROCKET = ' => ' COLON = ':' SPACED_COLON = ': ' # Checks whether the `pair` uses a hash rocket delimiter. # # @return [Boolean] whether this `pair` uses a hash rocket delimiter def hash_rocket? loc.operator.is?(HASH_ROCKET) end # Checks whether the `pair` uses a colon delimiter. # # @return [Boolean] whether this `pair` uses a colon delimiter def colon? loc.operator.is?(COLON) end # Returns the delimiter of the `pair` as a string. Returns `=>` for a # colon delimited `pair` and `:` for a hash rocket delimited `pair`. # # @param [Boolean] with_spacing whether to include spacing # @return [String] the delimiter of the `pair` def delimiter(*deprecated, with_spacing: deprecated.first) if with_spacing hash_rocket? ? SPACED_HASH_ROCKET : SPACED_COLON else hash_rocket? ? HASH_ROCKET : COLON end end # Returns the inverse delimiter of the `pair` as a string. # # @param [Boolean] with_spacing whether to include spacing # @return [String] the inverse delimiter of the `pair` def inverse_delimiter(*deprecated, with_spacing: deprecated.first) if with_spacing hash_rocket? ? SPACED_COLON : SPACED_HASH_ROCKET else hash_rocket? ? COLON : HASH_ROCKET end end # Checks whether the value starts on its own line. # # @return [Boolean] whether the value in the `pair` starts its own line def value_on_new_line? key.loc.line != value.loc.line end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/range_node.rb000066400000000000000000000006531373365104000223420ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `irange` and `erange` nodes. This will be used in # place of a plain node when the builder constructs the AST, making its # methods available to all `irange` and `erange` nodes within RuboCop. class RangeNode < Node def begin node_parts[0] end def end node_parts[1] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/regexp_node.rb000066400000000000000000000046311373365104000225400ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `regexp` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `regexp` nodes within RuboCop. class RegexpNode < Node OPTIONS = { x: Regexp::EXTENDED, i: Regexp::IGNORECASE, m: Regexp::MULTILINE, n: Regexp::NOENCODING, o: 0 }.freeze # @return [Regexp] a regexp of this node def to_regexp Regexp.new(content, options) end # @return [RuboCop::AST::Node] a regopt node def regopt children.last end # Note: The 'o' option is ignored. # # @return [Integer] the Regexp option bits as returned by Regexp#options def options regopt.children.map { |opt| OPTIONS.fetch(opt) }.inject(0, :|) end # @return [String] a string of regexp content def content children.select(&:str_type?).map(&:str_content).join end # @return [Bool] if the regexp is a /.../ literal def slash_literal? loc.begin.source == '/' end # @return [Bool] if the regexp is a %r{...} literal (using any delimiters) def percent_r_literal? !slash_literal? end # @return [String] the regexp delimiters (without %r) def delimiters [loc.begin.source[-1], loc.end.source[0]] end # @return [Bool] if char is one of the delimiters def delimiter?(char) delimiters.include?(char) end # @return [Bool] if regexp contains interpolation def interpolation? children.any?(&:begin_type?) end # @return [Bool] if regexp uses the multiline regopt def multiline_mode? regopt_include?(:m) end # @return [Bool] if regexp uses the extended regopt def extended? regopt_include?(:x) end # @return [Bool] if regexp uses the ignore-case regopt def ignore_case? regopt_include?(:i) end # @return [Bool] if regexp uses the single-interpolation regopt def single_interpolation? regopt_include?(:o) end # @return [Bool] if regexp uses the no-encoding regopt def no_encoding? regopt_include?(:n) end private def regopt_include?(option) regopt.children.include?(option) end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/resbody_node.rb000066400000000000000000000024121373365104000227100ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `resbody` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `resbody` nodes within RuboCop. class ResbodyNode < Node # Returns the body of the `rescue` clause. # # @return [Node, nil] The body of the `resbody`. def body node_parts[2] end # Returns an array of all the exceptions in the `rescue` clause. # # @return [Array] an array of exception nodes def exceptions exceptions_node = node_parts[0] if exceptions_node.nil? [] elsif exceptions_node.array_type? exceptions_node.values else [exceptions_node] end end # Returns the exception variable of the `rescue` clause. # # @return [Node, nil] The exception variable of the `resbody`. def exception_variable node_parts[1] end # Returns the index of the `resbody` branch within the exception handling statement. # # @return [Integer] the index of the `resbody` branch def branch_index parent.resbody_branches.index(self) end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/rescue_node.rb000066400000000000000000000030761373365104000225360ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `rescue` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `rescue` nodes within RuboCop. class RescueNode < Node # Returns the body of the rescue node. # # @return [Node, nil] The body of the rescue node. def body node_parts[0] end # Returns an array of all the rescue branches in the exception handling statement. # # @return [Array] an array of `resbody` nodes def resbody_branches node_parts[1...-1] end # Returns an array of all the rescue branches in the exception handling statement. # # @return [Array] an array of the bodies of the rescue branches # and the else (if any). Note that these bodies could be nil. def branches bodies = resbody_branches.map(&:body) bodies.push(else_branch) if else? bodies end # Returns the else branch of the exception handling statement, if any. # # @return [Node] the else branch node of the exception handling statement # @return [nil] if the exception handling statement does not have an else branch. def else_branch node_parts[-1] end # Checks whether this exception handling statement has an `else` branch. # # @return [Boolean] whether the exception handling statement has an `else` branch def else? loc.else end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/return_node.rb000066400000000000000000000005451373365104000225650ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `return` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `return` nodes within RuboCop. class ReturnNode < Node include ParameterizedNode::WrappedArguments end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/self_class_node.rb000066400000000000000000000011661373365104000233640ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `sclass` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `sclass` nodes within RuboCop. class SelfClassNode < Node # The identifer for this `sclass` node. (Always `self`.) # # @return [Node] the identifer of the class def identifier node_parts[0] end # The body of this `sclass` node. # # @return [Node, nil] the body of the class def body node_parts[1] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/send_node.rb000066400000000000000000000012411373365104000221710ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `send` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `send` nodes within RuboCop. class SendNode < Node include ParameterizedNode::RestArguments include MethodDispatchNode def_node_matcher :attribute_accessor?, <<~PATTERN [(send nil? ${:attr_reader :attr_writer :attr_accessor :attr} $...) (_ _ _ _ ...)] PATTERN private def first_argument_index 2 end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/str_node.rb000066400000000000000000000006531373365104000220560ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `str`, `dstr`, and `xstr` nodes. This will be used # in place of a plain node when the builder constructs the AST, making # its methods available to all `str` nodes within RuboCop. class StrNode < Node include BasicLiteralNode def heredoc? loc.is_a?(Parser::Source::Map::Heredoc) end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/super_node.rb000066400000000000000000000012611373365104000224000ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `super`- and `zsuper` nodes. This will be used in # place of a plain node when the builder constructs the AST, making its # methods available to all `super`- and `zsuper` nodes within RuboCop. class SuperNode < Node include ParameterizedNode include MethodDispatchNode # Custom destructuring method. This can be used to normalize # destructuring for different variations of the node. # # @return [Array] the different parts of the `super` node def node_parts [nil, :super, *to_a] end alias arguments children end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/symbol_node.rb000066400000000000000000000005151373365104000225500ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `sym` nodes. This will be used in place of a # plain node when the builder constructs the AST, making its methods # available to all `sym` nodes within RuboCop. class SymbolNode < Node include BasicLiteralNode end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/until_node.rb000066400000000000000000000017401373365104000223770ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `until` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `until` nodes within RuboCop. class UntilNode < Node include ConditionalNode include ModifierNode # Returns the keyword of the `until` statement as a string. # # @return [String] the keyword of the `until` statement def keyword 'until' end # Returns the inverse keyword of the `until` node as a string. # Returns `while` for `until` nodes and vice versa. # # @return [String] the inverse keyword of the `until` statement def inverse_keyword 'while' end # Checks whether the `until` node has a `do` keyword. # # @return [Boolean] whether the `until` node has a `do` keyword def do? loc.begin&.is?('do') end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/when_node.rb000066400000000000000000000023611373365104000222050ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `when` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `when` nodes within RuboCop. class WhenNode < Node # Returns an array of all the conditions in the `when` branch. # # @return [Array] an array of condition nodes def conditions node_parts[0...-1] end # @deprecated Use `conditions.each` def each_condition(&block) return conditions.to_enum(__method__) unless block_given? conditions.each(&block) self end # Returns the index of the `when` branch within the `case` statement. # # @return [Integer] the index of the `when` branch def branch_index parent.when_branches.index(self) end # Checks whether the `when` node has a `then` keyword. # # @return [Boolean] whether the `when` node has a `then` keyword def then? loc.begin&.is?('then') end # Returns the body of the `when` node. # # @return [Node, nil] the body of the `when` node def body node_parts[-1] end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/while_node.rb000066400000000000000000000017401373365104000223540ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `while` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `while` nodes within RuboCop. class WhileNode < Node include ConditionalNode include ModifierNode # Returns the keyword of the `while` statement as a string. # # @return [String] the keyword of the `while` statement def keyword 'while' end # Returns the inverse keyword of the `while` node as a string. # Returns `until` for `while` nodes and vice versa. # # @return [String] the inverse keyword of the `while` statement def inverse_keyword 'until' end # Checks whether the `until` node has a `do` keyword. # # @return [Boolean] whether the `until` node has a `do` keyword def do? loc.begin&.is?('do') end end end end rubocop-ast-0.6.0/lib/rubocop/ast/node/yield_node.rb000066400000000000000000000012241373365104000223470ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `yield` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `yield` nodes within RuboCop. class YieldNode < Node include ParameterizedNode include MethodDispatchNode # Custom destructuring method. This can be used to normalize # destructuring for different variations of the node. # # @return [Array] the different parts of the `send` node def node_parts [nil, :yield, *to_a] end alias arguments children end end end rubocop-ast-0.6.0/lib/rubocop/ast/node_pattern.rb000066400000000000000000001047701373365104000220030ustar00rootroot00000000000000# frozen_string_literal: true require 'delegate' require 'erb' # rubocop:disable Metrics/ClassLength, Metrics/CyclomaticComplexity module RuboCop module AST # This class performs a pattern-matching operation on an AST node. # # Initialize a new `NodePattern` with `NodePattern.new(pattern_string)`, then # pass an AST node to `NodePattern#match`. Alternatively, use one of the class # macros in `NodePattern::Macros` to define your own pattern-matching method. # # If the match fails, `nil` will be returned. If the match succeeds, the # return value depends on whether a block was provided to `#match`, and # whether the pattern contained any "captures" (values which are extracted # from a matching AST.) # # - With block: #match yields the captures (if any) and passes the return # value of the block through. # - With no block, but one capture: the capture is returned. # - With no block, but multiple captures: captures are returned as an array. # - With no block and no captures: #match returns `true`. # # ## Pattern string format examples # # ':sym' # matches a literal symbol # '1' # matches a literal integer # 'nil' # matches a literal nil # 'send' # matches (send ...) # '(send)' # matches (send) # '(send ...)' # matches (send ...) # '(op-asgn)' # node types with hyphenated names also work # '{send class}' # matches (send ...) or (class ...) # '({send class})' # matches (send) or (class) # '(send const)' # matches (send (const ...)) # '(send _ :new)' # matches (send :new) # '(send $_ :new)' # as above, but whatever matches the $_ is captured # '(send $_ $_)' # you can use as many captures as you want # '(send !const ...)' # ! negates the next part of the pattern # '$(send const ...)' # arbitrary matching can be performed on a capture # '(send _recv _msg)' # wildcards can be named (for readability) # '(send ... :new)' # you can match against the last children # '(array )' # you can match children in any order. This # # would match `['x', :y]` as well as `[:y, 'x'] # '(_ )' # will match if arguments have at least a `str` and # # a `sym` node, but can have more. # '(array <$str $_>)' # captures are in the order of the pattern, # # irrespective of the actual order of the children # '(array int*)' # will match an array of 0 or more integers # '(array int ?)' # will match 0 or 1 integer. # # Note: Space needed to distinguish from int? # '(array int+)' # will match an array of 1 or more integers # '(array (int $_)+)' # as above and will capture the numbers in an array # '(send $...)' # capture all the children as an array # '(send $... int)' # capture all children but the last as an array # '(send _x :+ _x)' # unification is performed on named wildcards # # (like Prolog variables...) # # (#== is used to see if values unify) # '(int odd?)' # words which end with a ? are predicate methods, # # are are called on the target to see if it matches # # any Ruby method which the matched object supports # # can be used # # if a truthy value is returned, the match succeeds # '(int [!1 !2])' # [] contains multiple patterns, ALL of which must # # match in that position # # in other words, while {} is pattern union (logical # # OR), [] is intersection (logical AND) # '(send %1 _)' # % stands for a parameter which must be supplied to # # #match at matching time # # it will be compared to the corresponding value in # # the AST using #=== so you can pass Procs, Regexp, # # etc. in addition to Nodes or literals. # # `Array#===` will never match a node element, but # # `Set#===` is an alias to `Set#include?` (Ruby 2.5+ # # only), and so can be very useful to match within # # many possible literals / Nodes. # # a bare '%' is the same as '%1' # # the number of extra parameters passed to #match # # must equal the highest % value in the pattern # # for consistency, %0 is the 'root node' which is # # passed as the 1st argument to #match, where the # # matching process starts # '(send _ %named)' # arguments can also be passed as named # # parameters (see `%1`) # # Note that the macros `def_node_matcher` and # # `def_node_search` accept default values for these. # '(send _ %CONST)' # the named constant will act like `%1` and `%named`. # '^^send' # each ^ ascends one level in the AST # # so this matches against the grandparent node # '`send' # descends any number of level in the AST # # so this matches against any descendant node # '#method' # we call this a 'funcall'; it calls a method in the # # context where a pattern-matching method is defined # # if that returns a truthy value, the match succeeds # 'equal?(%1)' # predicates can be given 1 or more extra args # '#method(%0, 1)' # funcalls can also be given 1 or more extra args # # These arguments can be patterns themselves, in # # which case a matcher responding to === will be # # passed. # '# comment' # comments are accepted at the end of lines # # You can nest arbitrarily deep: # # # matches node parsed from 'Const = Class.new' or 'Const = Module.new': # '(casgn nil? :Const (send (const nil? {:Class :Module}) :new))' # # matches a node parsed from an 'if', with a '==' comparison, # # and no 'else' branch: # '(if (send _ :== _) _ nil?)' # # Note that patterns like 'send' are implemented by calling `#send_type?` on # the node being matched, 'const' by `#const_type?`, 'int' by `#int_type?`, # and so on. Therefore, if you add methods which are named like # `#prefix_type?` to the AST node class, then 'prefix' will become usable as # a pattern. class NodePattern # @private Invalid = Class.new(StandardError) # @private # Builds Ruby code which implements a pattern class Compiler SYMBOL = %r{:(?:[\w+@*/?!<>=~|%^-]+|\[\]=?)}.freeze IDENTIFIER = /[a-zA-Z_][a-zA-Z0-9_-]*/.freeze COMMENT = /#\s.*$/.freeze META = Regexp.union( %w"( ) { } [ ] $< < > $... $ ! ^ ` ... + * ?" ).freeze NUMBER = /-?\d+(?:\.\d+)?/.freeze STRING = /".+?"/.freeze METHOD_NAME = /\#?#{IDENTIFIER}[!?]?\(?/.freeze PARAM_CONST = /%[A-Z:][a-zA-Z_:]+/.freeze KEYWORD_NAME = /%[a-z_]+/.freeze PARAM_NUMBER = /%\d*/.freeze SEPARATORS = /\s+/.freeze ONLY_SEPARATOR = /\A#{SEPARATORS}\Z/.freeze TOKENS = Regexp.union(META, PARAM_CONST, KEYWORD_NAME, PARAM_NUMBER, NUMBER, METHOD_NAME, SYMBOL, STRING) TOKEN = /\G(?:#{SEPARATORS}|#{TOKENS}|.)/.freeze NODE = /\A#{IDENTIFIER}\Z/.freeze PREDICATE = /\A#{IDENTIFIER}\?\(?\Z/.freeze WILDCARD = /\A_(?:#{IDENTIFIER})?\Z/.freeze FUNCALL = /\A\##{METHOD_NAME}/.freeze LITERAL = /\A(?:#{SYMBOL}|#{NUMBER}|#{STRING})\Z/.freeze PARAM = /\A#{PARAM_NUMBER}\Z/.freeze CONST = /\A#{PARAM_CONST}\Z/.freeze KEYWORD = /\A#{KEYWORD_NAME}\Z/.freeze CLOSING = /\A(?:\)|\}|\])\Z/.freeze REST = '...' CAPTURED_REST = '$...' attr_reader :match_code, :tokens, :captures SEQ_HEAD_INDEX = -1 # Placeholders while compiling, see with_..._context methods CUR_PLACEHOLDER = '@@@cur' CUR_NODE = "#{CUR_PLACEHOLDER} node@@@" CUR_ELEMENT = "#{CUR_PLACEHOLDER} element@@@" SEQ_HEAD_GUARD = '@@@seq guard head@@@' MULTIPLE_CUR_PLACEHOLDER = /#{CUR_PLACEHOLDER}.*#{CUR_PLACEHOLDER}/.freeze line = __LINE__ ANY_ORDER_TEMPLATE = ERB.new <<~RUBY.gsub("-%>\n", '%>') <% if capture_rest %>(<%= capture_rest %> = []) && <% end -%> <% if capture_all %>(<%= capture_all %> = <% end -%> <%= CUR_NODE %>.children[<%= range %>]<% if capture_all %>)<% end -%> .each_with_object({}) { |<%= child %>, <%= matched %>| case <% patterns.each_with_index do |pattern, i| -%> when !<%= matched %>[<%= i %>] && <%= with_context(pattern, child, use_temp_node: false) %> then <%= matched %>[<%= i %>] = true <% end -%> <% if !rest %> else break({}) <% elsif capture_rest %> else <%= capture_rest %> << <%= child %> <% end -%> end }.size == <%= patterns.size -%> RUBY ANY_ORDER_TEMPLATE.location = [__FILE__, line + 1] line = __LINE__ REPEATED_TEMPLATE = ERB.new <<~RUBY.gsub("-%>\n", '%>') <% if captured %>(<%= accumulate %> = Array.new) && <% end %> <%= CUR_NODE %>.children[<%= range %>].all? do |<%= child %>| <%= with_context(expr, child, use_temp_node: false) %><% if captured %>&& <%= accumulate %>.push(<%= captured %>)<% end %> end <% if captured %>&& (<%= captured %> = if <%= accumulate %>.empty? <%= captured %>.map{[]} # Transpose hack won't work for empty case else <%= accumulate %>.transpose end) <% end -%> RUBY REPEATED_TEMPLATE.location = [__FILE__, line + 1] def initialize(str, root = 'node0', node_var = root) @string = str # For def_node_matcher, root == node_var # For def_node_search, root is the root node to search on, # and node_var is the current descendant being searched. @root = root @node_var = node_var @temps = 0 # avoid name clashes between temp variables @captures = 0 # number of captures seen @unify = {} # named wildcard -> temp variable @params = 0 # highest % (param) number seen @keywords = Set[] # keyword parameters seen run end def run @tokens = Compiler.tokens(@string) @match_code = with_context(compile_expr, @node_var, use_temp_node: false) @match_code.prepend("(captures = Array.new(#{@captures})) && ") \ if @captures.positive? fail_due_to('unbalanced pattern') unless tokens.empty? end # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def compile_expr(token = tokens.shift) # read a single pattern-matching expression from the token stream, # return Ruby code which performs the corresponding matching operation # # the 'pattern-matching' expression may be a composite which # contains an arbitrary number of sub-expressions, but that composite # must all have precedence higher or equal to that of `&&` # # Expressions may use placeholders like: # CUR_NODE: Ruby code that evaluates to an AST node # CUR_ELEMENT: Either the node or the type if in first element of # a sequence (aka seq_head, e.g. "(seq_head first_node_arg ...") if (atom = compile_atom(token)) return atom_to_expr(atom) end case token when '(' then compile_seq when '{' then compile_union when '[' then compile_intersect when '!' then compile_negation when '$' then compile_capture when '^' then compile_ascend when '`' then compile_descend when WILDCARD then compile_new_wildcard(token[1..-1]) when FUNCALL then compile_funcall(token) when PREDICATE then compile_predicate(token) when NODE then compile_nodetype(token) else fail_due_to("invalid token #{token.inspect}") end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize def tokens_until(stop, what) return to_enum __method__, stop, what unless block_given? fail_due_to("empty #{what}") if tokens.first == stop yield until tokens.first == stop tokens.shift end def compile_seq terms = tokens_until(')', 'sequence').map { variadic_seq_term } Sequence.new(self, *terms).compile end def compile_guard_clause "#{CUR_NODE}.is_a?(RuboCop::AST::Node)" end def variadic_seq_term token = tokens.shift case token when CAPTURED_REST then compile_captured_ellipsis when REST then compile_ellipsis when '$<' then compile_any_order(next_capture) when '<' then compile_any_order else compile_repeated_expr(token) end end def compile_repeated_expr(token) before = @captures expr = compile_expr(token) min, max = parse_repetition_token return [1, expr] if min.nil? if @captures != before captured = "captures[#{before}...#{@captures}]" accumulate = next_temp_variable(:accumulate) end arity = min..max || Float::INFINITY [arity, repeated_generator(expr, captured, accumulate)] end def repeated_generator(expr, captured, accumulate) with_temp_variables do |child| lambda do |range| fail_due_to 'repeated pattern at beginning of sequence' if range.begin == SEQ_HEAD_INDEX REPEATED_TEMPLATE.result(binding) end end end def parse_repetition_token case tokens.first when '*' then min = 0 when '+' then min = 1 when '?' then min = 0 max = 1 else return end tokens.shift [min, max] end # @private # Builds Ruby code for a sequence # (head *first_terms variadic_term *last_terms) class Sequence extend Forwardable def_delegators :@compiler, :compile_guard_clause, :with_seq_head_context, :with_child_context, :fail_due_to def initialize(compiler, *arity_term_list) @arities, @terms = arity_term_list.transpose @compiler = compiler @variadic_index = @arities.find_index { |a| a.is_a?(Range) } fail_due_to 'multiple variable patterns in same sequence' \ if @variadic_index && !@arities.one? { |a| a.is_a?(Range) } end def compile [ compile_guard_clause, compile_child_nb_guard, compile_seq_head, *compile_first_terms, compile_variadic_term, *compile_last_terms ].compact.join(" &&\n") << SEQ_HEAD_GUARD end private def first_terms_arity first_terms_range { |r| @arities[r].inject(0, :+) } || 0 end def last_terms_arity last_terms_range { |r| @arities[r].inject(0, :+) } || 0 end def variadic_term_min_arity @variadic_index ? @arities[@variadic_index].begin : 0 end def first_terms_range yield 1..(@variadic_index || @terms.size) - 1 if seq_head? end def last_terms_range yield @variadic_index + 1...@terms.size if @variadic_index end def seq_head? @variadic_index != 0 end def compile_child_nb_guard fixed = first_terms_arity + last_terms_arity min = fixed + variadic_term_min_arity op = if @variadic_index max_variadic = @arities[@variadic_index].end if max_variadic != Float::INFINITY range = min..fixed + max_variadic return "(#{range}).cover?(#{CUR_NODE}.children.size)" end '>=' else '==' end "#{CUR_NODE}.children.size #{op} #{min}" end def term(index, range) t = @terms[index] if t.respond_to? :call t.call(range) else with_child_context(t, range.begin) end end def compile_seq_head return unless seq_head? fail_due_to 'sequences cannot start with <' \ if @terms[0].respond_to? :call with_seq_head_context(@terms[0]) end def compile_first_terms first_terms_range { |range| compile_terms(range, 0) } end def compile_last_terms last_terms_range { |r| compile_terms(r, -last_terms_arity) } end def compile_terms(index_range, start) index_range.map do |i| current = start start += @arities.fetch(i) term(i, current..start - 1) end end def compile_variadic_term variadic_arity { |arity| term(@variadic_index, arity) } end def variadic_arity return unless @variadic_index first = @variadic_index.positive? ? first_terms_arity : SEQ_HEAD_INDEX yield first..-last_terms_arity - 1 end end private_constant :Sequence def compile_captured_ellipsis capture = next_capture block = lambda { |range| # Consider ($...) like (_ $...): range = 0..range.end if range.begin == SEQ_HEAD_INDEX "(#{capture} = #{CUR_NODE}.children[#{range}])" } [0..Float::INFINITY, block] end def compile_ellipsis [0..Float::INFINITY, 'true'] end # rubocop:disable Metrics/MethodLength def compile_any_order(capture_all = nil) rest = capture_rest = nil patterns = [] with_temp_variables do |child, matched| tokens_until('>', 'any child') do fail_due_to 'ellipsis must be at the end of <>' if rest token = tokens.shift case token when CAPTURED_REST then rest = capture_rest = next_capture when REST then rest = true else patterns << compile_expr(token) end end [rest ? patterns.size..Float::INFINITY : patterns.size, ->(range) { ANY_ORDER_TEMPLATE.result(binding) }] end end # rubocop:enable Metrics/MethodLength def insure_same_captures(enum, what) return to_enum __method__, enum, what unless block_given? captures_before = captures_after = nil enum.each do captures_before ||= @captures @captures = captures_before yield captures_after ||= @captures fail_due_to("each #{what} must have same # of captures") if captures_after != @captures end end def access_unify(name) var = @unify[name] if var == :forbidden_unification fail_due_to "Wildcard #{name} was first seen in a subset of a" \ " union and can't be used outside that union" end var end def forbid_unification(*names) names.each do |name| @unify[name] = :forbidden_unification end end # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def unify_in_union(enum) # We need to reset @unify before each branch is processed. # Moreover we need to keep track of newly encountered wildcards. # Var `new_unify_intersection` will hold those that are encountered # in all branches; these are not a problem. # Var `partial_unify` will hold those encountered in only a subset # of the branches; these can't be used outside of the union. return to_enum __method__, enum unless block_given? new_unify_intersection = nil partial_unify = [] unify_before = @unify.dup result = enum.each do |e| @unify = unify_before.dup if new_unify_intersection yield e new_unify = @unify.keys - unify_before.keys if new_unify_intersection.nil? # First iteration new_unify_intersection = new_unify else union = new_unify_intersection | new_unify new_unify_intersection &= new_unify partial_unify |= union - new_unify_intersection end end # At this point, all members of `new_unify_intersection` can be used # for unification outside of the union, but partial_unify may not forbid_unification(*partial_unify) result end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize def compile_union # we need to ensure that each branch of the {} contains the same # number of captures (since only one branch of the {} can actually # match, the same variables are used to hold the captures for each # branch) enum = tokens_until('}', 'union') enum = unify_in_union(enum) terms = insure_same_captures(enum, 'branch of {}') .map { compile_expr } "(#{terms.join(' || ')})" end def compile_intersect tokens_until(']', 'intersection') .map { compile_expr } .join(' && ') end def compile_capture "(#{next_capture} = #{CUR_ELEMENT}; #{compile_expr})" end def compile_negation "!(#{compile_expr})" end def compile_ascend with_context("#{CUR_NODE} && #{compile_expr}", "#{CUR_NODE}.parent") end def compile_descend with_temp_variables do |descendant| pattern = with_context(compile_expr, descendant, use_temp_node: false) [ "RuboCop::AST::NodePattern.descend(#{CUR_ELEMENT}).", "any? do |#{descendant}|", " #{pattern}", 'end' ].join("\n") end end # Known wildcards are considered atoms, see `compile_atom` def compile_new_wildcard(name) return 'true' if name.empty? n = @unify[name] = "unify_#{name.gsub('-', '__')}" # double assign to avoid "assigned but unused variable" "(#{n} = #{CUR_ELEMENT}; #{n} = #{n}; true)" end def compile_predicate(predicate) if predicate.end_with?('(') # is there an arglist? args = compile_args predicate = predicate[0..-2] # drop the trailing ( "#{CUR_ELEMENT}.#{predicate}(#{args.join(',')})" else "#{CUR_ELEMENT}.#{predicate}" end end def compile_funcall(method) # call a method in the context which this pattern-matching # code is used in. pass target value as an argument method = method[1..-1] # drop the leading # if method.end_with?('(') # is there an arglist? args = compile_args method = method[0..-2] # drop the trailing ( "#{method}(#{CUR_ELEMENT},#{args.join(',')})" else "#{method}(#{CUR_ELEMENT})" end end def compile_nodetype(type) "#{compile_guard_clause} && #{CUR_NODE}.#{type.tr('-', '_')}_type?" end def compile_args tokens_until(')', 'call arguments').map do arg = compile_arg tokens.shift if tokens.first == ',' arg end end def atom_to_expr(atom) "#{atom} === #{CUR_ELEMENT}" end def expr_to_atom(expr) with_temp_variables do |compare| in_context = with_context(expr, compare, use_temp_node: false) "::RuboCop::AST::NodePattern::Matcher.new{|#{compare}| #{in_context}}" end end # @return compiled atom (e.g. ":literal" or "SOME_CONST") # or nil if not a simple atom (unknown wildcard, other tokens) def compile_atom(token) case token when WILDCARD then access_unify(token[1..-1]) # could be nil when LITERAL then token when KEYWORD then get_keyword(token[1..-1]) when CONST then get_const(token[1..-1]) when PARAM then get_param(token[1..-1]) when CLOSING then fail_due_to("#{token} in invalid position") when nil then fail_due_to('pattern ended prematurely') end end def compile_arg token = tokens.shift compile_atom(token) || expr_to_atom(compile_expr(token)) end def next_capture index = @captures @captures += 1 "captures[#{index}]" end def get_param(number) number = number.empty? ? 1 : Integer(number) @params = number if number > @params number.zero? ? @root : "param#{number}" end def get_keyword(name) @keywords << name name end def get_const(const) const # Output the constant exactly as given end def emit_yield_capture(when_no_capture = '') yield_val = if @captures.zero? when_no_capture elsif @captures == 1 'captures[0]' # Circumvent https://github.com/jruby/jruby/issues/5710 else '*captures' end "yield(#{yield_val})" end def emit_retval if @captures.zero? 'true' elsif @captures == 1 'captures[0]' else 'captures' end end def emit_param_list (1..@params).map { |n| "param#{n}" }.join(',') end def emit_keyword_list(forwarding: false) pattern = "%s: #{'%s' if forwarding}" @keywords.map { |k| format(pattern, keyword: k) }.join(',') end def emit_params(*first, forwarding: false) params = emit_param_list keywords = emit_keyword_list(forwarding: forwarding) [*first, params, keywords].reject(&:empty?).join(',') end def emit_method_code <<~RUBY return unless #{@match_code} block_given? ? #{emit_yield_capture} : (return #{emit_retval}) RUBY end def fail_due_to(message) raise Invalid, "Couldn't compile due to #{message}. Pattern: #{@string}" end def with_temp_node(cur_node) with_temp_variables do |node| yield "(#{node} = #{cur_node})", node end .gsub("\n", "\n ") # Nicer indent for debugging end def with_temp_variables(&block) names = block.parameters.map { |_, name| next_temp_variable(name) } yield(*names) end def next_temp_variable(name) "#{name}#{next_temp_value}" end def next_temp_value @temps += 1 end def auto_use_temp_node?(code) code.match?(MULTIPLE_CUR_PLACEHOLDER) end # with_<...>_context methods are used whenever the context, # i.e the current node or the current element can be determined. def with_child_context(code, child_index) with_context(code, "#{CUR_NODE}.children[#{child_index}]") end def with_context(code, cur_node, use_temp_node: auto_use_temp_node?(code)) if use_temp_node with_temp_node(cur_node) do |init, temp_var| substitute_cur_node(code, temp_var, first_cur_node: init) end else substitute_cur_node(code, cur_node) end end def with_seq_head_context(code) fail_due_to('parentheses at sequence head') if code.include?(SEQ_HEAD_GUARD) code.gsub CUR_ELEMENT, "#{CUR_NODE}.type" end def substitute_cur_node(code, cur_node, first_cur_node: cur_node) iter = 0 code .gsub(CUR_ELEMENT, CUR_NODE) .gsub(CUR_NODE) do iter += 1 iter == 1 ? first_cur_node : cur_node end .gsub(SEQ_HEAD_GUARD, '') end def self.tokens(pattern) pattern.gsub(COMMENT, '').scan(TOKEN).grep_v(ONLY_SEPARATOR) end # This method minimizes the closure for our method def wrapping_block(method_name, **defaults) proc do |*args, **values| send method_name, *args, **defaults, **values end end def def_helper(base, method_name, **defaults) location = caller_locations(3, 1).first unless defaults.empty? call = :"without_defaults_#{method_name}" base.send :define_method, method_name, &wrapping_block(call, **defaults) method_name = call end src = yield method_name base.class_eval(src, location.path, location.lineno) end def def_node_matcher(base, method_name, **defaults) def_helper(base, method_name, **defaults) do |name| <<~RUBY def #{name}(#{emit_params('node = self')}) #{emit_method_code} end RUBY end end def def_node_search(base, method_name, **defaults) def_helper(base, method_name, **defaults) do |name| emit_node_search(name) end end def emit_node_search(method_name) if method_name.to_s.end_with?('?') on_match = 'return true' else args = emit_params(":#{method_name}", @root, forwarding: true) prelude = "return enum_for(#{args}) unless block_given?\n" on_match = emit_yield_capture(@node_var) end emit_node_search_body(method_name, prelude: prelude, on_match: on_match) end def emit_node_search_body(method_name, prelude:, on_match:) <<~RUBY def #{method_name}(#{emit_params(@root)}) #{prelude} #{@root}.each_node do |#{@node_var}| if #{match_code} #{on_match} end end nil end RUBY end end private_constant :Compiler # Helpers for defining methods based on a pattern string module Macros # Define a method which applies a pattern to an AST node # # The new method will return nil if the node does not match # If the node matches, and a block is provided, the new method will # yield to the block (passing any captures as block arguments). # If the node matches, and no block is provided, the new method will # return the captures, or `true` if there were none. def def_node_matcher(method_name, pattern_str, **keyword_defaults) Compiler.new(pattern_str, 'node') .def_node_matcher(self, method_name, **keyword_defaults) end # Define a method which recurses over the descendants of an AST node, # checking whether any of them match the provided pattern # # If the method name ends with '?', the new method will return `true` # as soon as it finds a descendant which matches. Otherwise, it will # yield all descendants which match. def def_node_search(method_name, pattern_str, **keyword_defaults) Compiler.new(pattern_str, 'node0', 'node') .def_node_search(self, method_name, **keyword_defaults) end end attr_reader :pattern def initialize(str) @pattern = str compiler = Compiler.new(str, 'node0') src = "def match(#{compiler.emit_params('node0')});" \ "#{compiler.emit_method_code}end" instance_eval(src, __FILE__, __LINE__ + 1) end def match(*args, **rest) # If we're here, it's because the singleton method has not been defined, # either because we've been dup'ed or serialized through YAML initialize(pattern) if rest.empty? match(*args) else match(*args, **rest) end end def marshal_load(pattern) initialize pattern end def marshal_dump pattern end def ==(other) other.is_a?(NodePattern) && Compiler.tokens(other.pattern) == Compiler.tokens(pattern) end alias eql? == def to_s "#<#{self.class} #{pattern}>" end # Yields its argument and any descendants, depth-first. # def self.descend(element, &block) return to_enum(__method__, element) unless block_given? yield element if element.is_a?(::RuboCop::AST::Node) element.children.each do |child| descend(child, &block) end end nil end # @api private class Matcher def initialize(&block) @block = block end def ===(compare) @block.call(compare) end end end end end # rubocop:enable Metrics/ClassLength, Metrics/CyclomaticComplexity rubocop-ast-0.6.0/lib/rubocop/ast/processed_source.rb000066400000000000000000000201061373365104000226560ustar00rootroot00000000000000# frozen_string_literal: true require 'digest/sha1' # rubocop:disable Metrics/ClassLength module RuboCop module AST # ProcessedSource contains objects which are generated by Parser # and other information such as disabled lines for cops. # It also provides a convenient way to access source lines. class ProcessedSource STRING_SOURCE_NAME = '(string)' attr_reader :path, :buffer, :ast, :comments, :tokens, :diagnostics, :parser_error, :raw_source, :ruby_version def self.from_file(path, ruby_version) file = File.read(path, mode: 'rb') new(file, ruby_version, path) end def initialize(source, ruby_version, path = nil) # Defaults source encoding to UTF-8, regardless of the encoding it has # been read with, which could be non-utf8 depending on the default # external encoding. source.force_encoding(Encoding::UTF_8) unless source.encoding == Encoding::UTF_8 @raw_source = source @path = path @diagnostics = [] @ruby_version = ruby_version @parser_error = nil parse(source, ruby_version) end def ast_with_comments return if !ast || !comments @ast_with_comments ||= Parser::Source::Comment.associate(ast, comments) end # Returns the source lines, line break characters removed, excluding a # possible __END__ and everything that comes after. def lines @lines ||= begin all_lines = @buffer.source_lines last_token_line = tokens.any? ? tokens.last.line : all_lines.size result = [] all_lines.each_with_index do |line, ix| break if ix >= last_token_line && line == '__END__' result << line end result end end def [](*args) lines[*args] end def valid_syntax? return false if @parser_error @diagnostics.none? { |d| %i[error fatal].include?(d.level) } end # Raw source checksum for tracking infinite loops. def checksum Digest::SHA1.hexdigest(@raw_source) end # @deprecated Use `comments.each` def each_comment(&block) comments.each(&block) end # @deprecated Use `comment_at_line`, `each_comment_in_lines`, or `comments.find` def find_comment(&block) comments.find(&block) end # @deprecated Use `tokens.each` def each_token(&block) tokens.each(&block) end # @deprecated Use `tokens.find` def find_token(&block) tokens.find(&block) end def file_path buffer.name end def blank? ast.nil? end # @return [Comment, nil] the comment at that line, if any. def comment_at_line(line) comment_index[line] end # @return [Boolean] if the given line number has a comment. def line_with_comment?(line) comment_index.include?(line) end # Enumerates on the comments contained with the given `line_range` def each_comment_in_lines(line_range) return to_enum(:each_comment_in_lines, line_range) unless block_given? line_range.each do |line| if (comment = comment_index[line]) yield comment end end end # @return [Boolean] if any of the lines in the given `source_range` has a comment. # Consider using `each_comment_in_lines` instead def contains_comment?(source_range) each_comment_in_lines(source_range.line..source_range.last_line).any? end # @deprecated use contains_comment? alias commented? contains_comment? # @deprecated Use `each_comment_in_lines` # Should have been called `comments_before_or_at_line`. Doubtful it has of any valid use. def comments_before_line(line) each_comment_in_lines(0..line).to_a end def start_with?(string) return false if self[0].nil? self[0].start_with?(string) end def preceding_line(token) lines[token.line - 2] end def current_line(token) lines[token.line - 1] end def following_line(token) lines[token.line] end def line_indentation(line_number) lines[line_number - 1] .match(/^(\s*)/)[1] .to_s .length end def tokens_within(range_or_node) begin_index = first_token_index(range_or_node) end_index = last_token_index(range_or_node) sorted_tokens[begin_index..end_index] end def first_token_of(range_or_node) sorted_tokens[first_token_index(range_or_node)] end def last_token_of(range_or_node) sorted_tokens[last_token_index(range_or_node)] end private def comment_index @comment_index ||= {}.tap do |hash| comments.each { |c| hash[c.location.line] = c } end end def parse(source, ruby_version) buffer_name = @path || STRING_SOURCE_NAME @buffer = Parser::Source::Buffer.new(buffer_name, 1) begin @buffer.source = source rescue EncodingError => e @parser_error = e @ast = nil @comments = [] @tokens = [] return end @ast, @comments, @tokens = tokenize(create_parser(ruby_version)) end def tokenize(parser) begin ast, comments, tokens = parser.tokenize(@buffer) ast ||= nil # force `false` to `nil`, see https://github.com/whitequark/parser/pull/722 rescue Parser::SyntaxError # All errors are in diagnostics. No need to handle exception. comments = [] tokens = [] end ast&.complete! tokens.map! { |t| Token.from_parser_token(t) } [ast, comments, tokens] end # rubocop:disable Metrics/MethodLength def parser_class(ruby_version) case ruby_version when 2.4 require 'parser/ruby24' Parser::Ruby24 when 2.5 require 'parser/ruby25' Parser::Ruby25 when 2.6 require 'parser/ruby26' Parser::Ruby26 when 2.7 require 'parser/ruby27' Parser::Ruby27 when 2.8, 3.0 require 'parser/ruby30' Parser::Ruby30 else raise ArgumentError, "RuboCop found unknown Ruby version: #{ruby_version.inspect}" end end # rubocop:enable Metrics/MethodLength def create_parser(ruby_version) builder = RuboCop::AST::Builder.new parser_class(ruby_version).new(builder).tap do |parser| # On JRuby there's a risk that we hang in tokenize() if we # don't set the all errors as fatal flag. The problem is caused by a bug # in Racc that is discussed in issue #93 of the whitequark/parser # project on GitHub. parser.diagnostics.all_errors_are_fatal = (RUBY_ENGINE != 'ruby') parser.diagnostics.ignore_warnings = false parser.diagnostics.consumer = lambda do |diagnostic| @diagnostics << diagnostic end end end def first_token_index(range_or_node) begin_pos = source_range(range_or_node).begin_pos sorted_tokens.bsearch_index { |token| token.begin_pos >= begin_pos } end def last_token_index(range_or_node) end_pos = source_range(range_or_node).end_pos sorted_tokens.bsearch_index { |token| token.end_pos >= end_pos } end # The tokens list is always sorted by token position, except for cases when heredoc # is passed as a method argument. In this case tokens are interleaved by # heredoc contents' tokens. def sorted_tokens # Use stable sort. @sorted_tokens ||= tokens.sort_by.with_index { |token, i| [token.begin_pos, i] } end def source_range(range_or_node) if range_or_node.respond_to?(:source_range) range_or_node.source_range else range_or_node end end end end end # rubocop:enable Metrics/ClassLength rubocop-ast-0.6.0/lib/rubocop/ast/rubocop_compatibility.rb000066400000000000000000000016261373365104000237170ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop # ... module AST # Responsible for compatibility with main gem # @api private module RuboCopCompatibility INCOMPATIBLE_COPS = { '0.89.0' => 'Layout/LineLength', '0.92.0' => 'Style/MixinUsage' }.freeze def rubocop_loaded loaded = Gem::Version.new(RuboCop::Version::STRING) incompatible = INCOMPATIBLE_COPS.select do |k, _v| loaded < Gem::Version.new(k) end.values return if incompatible.empty? warn <<~WARNING *** WARNING – Incompatible versions of `rubocop` and `rubocop-ast` You may encounter issues with the following \ Cop#{'s' if incompatible.size > 1}: #{incompatible.join(', ')} Please upgrade rubocop to at least v#{INCOMPATIBLE_COPS.keys.last} WARNING end end extend RuboCopCompatibility end end rubocop-ast-0.6.0/lib/rubocop/ast/sexp.rb000066400000000000000000000006051373365104000202700ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # This module provides a shorthand method to create a {Node} like # `Parser::AST::Sexp`. # # @see https://www.rubydoc.info/gems/ast/AST/Sexp module Sexp # Creates a {Node} with type `type` and children `children`. def s(type, *children) Node.new(type, children) end end end end rubocop-ast-0.6.0/lib/rubocop/ast/token.rb000066400000000000000000000040601373365104000204300ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A basic wrapper around Parser's tokens. class Token attr_reader :pos, :type, :text def self.from_parser_token(parser_token) type, details = parser_token text, range = details new(range, type, text) end def initialize(pos, type, text) @pos = pos @type = type # Parser token "text" may be an Integer @text = text.to_s end def line @pos.line end def column @pos.column end def begin_pos @pos.begin_pos end def end_pos @pos.end_pos end def to_s "[[#{line}, #{column}], #{type}, #{text.inspect}]" end # Checks if there is whitespace after token def space_after? pos.source_buffer.source.match(/\G\s/, end_pos) end # Checks if there is whitespace before token def space_before? position = begin_pos.zero? ? begin_pos : begin_pos - 1 pos.source_buffer.source.match(/\G\s/, position) end ## Type Predicates def comment? type == :tCOMMENT end def semicolon? type == :tSEMI end def left_array_bracket? type == :tLBRACK end def left_ref_bracket? type == :tLBRACK2 end def left_bracket? %i[tLBRACK tLBRACK2].include?(type) end def right_bracket? type == :tRBRACK end def left_brace? type == :tLBRACE end def left_curly_brace? type == :tLCURLY end def right_curly_brace? type == :tRCURLY end def left_parens? %i[tLPAREN tLPAREN2].include?(type) end def right_parens? type == :tRPAREN end def comma? type == :tCOMMA end def rescue_modifier? type == :kRESCUE_MOD end def end? type == :kEND end def equal_sign? %i[tEQL tOP_ASGN].include?(type) end end end end rubocop-ast-0.6.0/lib/rubocop/ast/traversal.rb000066400000000000000000000140161373365104000213150ustar00rootroot00000000000000# frozen_string_literal: true # rubocop:disable Metrics/ModuleLength module RuboCop module AST # Provides methods for traversing an AST. # Does not transform an AST; for that, use Parser::AST::Processor. # Override methods to perform custom processing. Remember to call `super` # if you want to recursively process descendant nodes. module Traversal def walk(node) return if node.nil? send(:"on_#{node.type}", node) nil end NO_CHILD_NODES = %i[true false nil int float complex rational str sym regopt self lvar ivar cvar gvar nth_ref back_ref cbase arg restarg blockarg shadowarg kwrestarg zsuper redo retry forward_args forwarded_args match_var match_nil_pattern empty_else forward_arg lambda procarg0 __ENCODING__].freeze ONE_CHILD_NODE = %i[splat kwsplat block_pass not break next preexe postexe match_current_line defined? arg_expr pin match_rest if_guard unless_guard match_with_trailing_comma].freeze MANY_CHILD_NODES = %i[dstr dsym xstr regexp array hash pair mlhs masgn or_asgn and_asgn rasgn mrasgn undef alias args super yield or and while_post until_post iflipflop eflipflop match_with_lvasgn begin kwbegin return in_match match_alt match_as array_pattern array_pattern_with_tail hash_pattern const_pattern find_pattern index indexasgn].freeze SECOND_CHILD_ONLY = %i[lvasgn ivasgn cvasgn gvasgn optarg kwarg kwoptarg].freeze NO_CHILD_NODES.each do |type| module_eval("def on_#{type}(node); end", __FILE__, __LINE__) end ONE_CHILD_NODE.each do |type| module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def on_#{type}(node) if (child = node.children[0]) send(:"on_\#{child.type}", child) end end RUBY end MANY_CHILD_NODES.each do |type| module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def on_#{type}(node) node.children.each { |child| send(:"on_\#{child.type}", child) } nil end RUBY end SECOND_CHILD_ONLY.each do |type| # Guard clause is for nodes nested within mlhs module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def on_#{type}(node) if (child = node.children[1]) send(:"on_\#{child.type}", child) end end RUBY end def on_const(node) return unless (child = node.children[0]) send(:"on_#{child.type}", child) end def on_casgn(node) children = node.children if (child = children[0]) # always const??? send(:"on_#{child.type}", child) end return unless (child = children[2]) send(:"on_#{child.type}", child) end def on_class(node) children = node.children child = children[0] # always const??? send(:"on_#{child.type}", child) if (child = children[1]) send(:"on_#{child.type}", child) end return unless (child = children[2]) send(:"on_#{child.type}", child) end def on_def(node) children = node.children on_args(children[1]) return unless (child = children[2]) send(:"on_#{child.type}", child) end def on_send(node) node.children.each_with_index do |child, i| next if i == 1 send(:"on_#{child.type}", child) if child end nil end alias on_csend on_send def on_op_asgn(node) children = node.children child = children[0] send(:"on_#{child.type}", child) child = children[2] send(:"on_#{child.type}", child) end def on_defs(node) children = node.children child = children[0] send(:"on_#{child.type}", child) on_args(children[2]) return unless (child = children[3]) send(:"on_#{child.type}", child) end def on_if(node) children = node.children child = children[0] send(:"on_#{child.type}", child) if (child = children[1]) send(:"on_#{child.type}", child) end return unless (child = children[2]) send(:"on_#{child.type}", child) end def on_while(node) children = node.children child = children[0] send(:"on_#{child.type}", child) return unless (child = children[1]) send(:"on_#{child.type}", child) end alias on_until on_while alias on_module on_while alias on_sclass on_while def on_block(node) children = node.children child = children[0] send(:"on_#{child.type}", child) # can be send, zsuper... on_args(children[1]) return unless (child = children[2]) send(:"on_#{child.type}", child) end def on_case(node) node.children.each do |child| send(:"on_#{child.type}", child) if child end nil end alias on_rescue on_case alias on_resbody on_case alias on_ensure on_case alias on_for on_case alias on_when on_case alias on_case_match on_case alias on_in_pattern on_case alias on_irange on_case alias on_erange on_case def on_numblock(node) children = node.children child = children[0] send(:"on_#{child.type}", child) return unless (child = children[2]) send(:"on_#{child.type}", child) end end end end # rubocop:enable Metrics/ModuleLength rubocop-ast-0.6.0/lib/rubocop/ast/version.rb000066400000000000000000000001671373365104000210010ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST module Version STRING = '0.6.0' end end end rubocop-ast-0.6.0/rubocop-ast.gemspec000066400000000000000000000031431373365104000175610ustar00rootroot00000000000000# frozen_string_literal: true $LOAD_PATH.unshift File.expand_path('lib', __dir__) require 'rubocop/ast/version' Gem::Specification.new do |s| s.name = 'rubocop-ast' s.version = RuboCop::AST::Version::STRING s.platform = Gem::Platform::RUBY s.required_ruby_version = '>= 2.3.0' s.authors = ['Bozhidar Batsov', 'Jonas Arvidsson', 'Yuji Nakayama'] s.description = <<-DESCRIPTION RuboCop's Node and NodePattern classes. DESCRIPTION s.email = 'rubocop@googlegroups.com' s.files = `git ls-files lib LICENSE.txt README.md` .split($RS) s.extra_rdoc_files = ['LICENSE.txt', 'README.md'] s.homepage = 'https://github.com/rubocop-hq/rubocop-ast' s.licenses = ['MIT'] s.summary = 'RuboCop tools to deal with Ruby code AST.' s.metadata = { 'homepage_uri' => 'https://www.rubocop.org/', 'changelog_uri' => 'https://github.com/rubocop-hq/rubocop-ast/blob/master/CHANGELOG.md', 'source_code_uri' => 'https://github.com/rubocop-hq/rubocop-ast/', 'documentation_uri' => 'https://docs.rubocop.org/rubocop-ast/', 'bug_tracker_uri' => 'https://github.com/rubocop-hq/rubocop-ast/issues' } s.add_runtime_dependency('parser', '>= 2.7.1.5') s.add_development_dependency('bundler', '>= 1.15.0', '< 3.0') # #### Do NOT add `rubocop` (or anything depending on `rubocop`), even as a # development_dependency to insure that this gem remains independent of `rubocop`. # Bundler loads all dependencies automatically, so specs wouldn't detect it if # we required a file from `rubocop` gem by mistake. # Note that `rake internal_investigation` will use `../rubocop` if present. end rubocop-ast-0.6.0/spec/000077500000000000000000000000001373365104000147075ustar00rootroot00000000000000rubocop-ast-0.6.0/spec/rubocop/000077500000000000000000000000001373365104000163605ustar00rootroot00000000000000rubocop-ast-0.6.0/spec/rubocop/ast/000077500000000000000000000000001373365104000171475ustar00rootroot00000000000000rubocop-ast-0.6.0/spec/rubocop/ast/alias_node_spec.rb000066400000000000000000000013271373365104000226070ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::AliasNode do let(:alias_node) { parse_source(source).ast } describe '.new' do let(:source) do 'alias foo bar' end it { expect(alias_node.is_a?(described_class)).to be(true) } end describe '#new_identifier' do let(:source) do 'alias foo bar' end it { expect(alias_node.new_identifier.sym_type?).to be(true) } it { expect(alias_node.new_identifier.children.first).to eq(:foo) } end describe '#old_identifier' do let(:source) do 'alias foo bar' end it { expect(alias_node.old_identifier.sym_type?).to be(true) } it { expect(alias_node.old_identifier.children.first).to eq(:bar) } end end rubocop-ast-0.6.0/spec/rubocop/ast/and_node_spec.rb000066400000000000000000000057451373365104000222700ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::AndNode do let(:and_node) { parse_source(source).ast } describe '.new' do context 'with a logical and node' do let(:source) do ':foo && :bar' end it { expect(and_node.is_a?(described_class)).to be(true) } end context 'with a semantic and node' do let(:source) do ':foo and :bar' end it { expect(and_node.is_a?(described_class)).to be(true) } end end describe '#logical_operator?' do context 'with a logical and node' do let(:source) do ':foo && :bar' end it { expect(and_node.logical_operator?).to be(true) } end context 'with a semantic and node' do let(:source) do ':foo and :bar' end it { expect(and_node.logical_operator?).to be(false) } end end describe '#semantic_operator?' do context 'with a logical and node' do let(:source) do ':foo && :bar' end it { expect(and_node.semantic_operator?).to be(false) } end context 'with a semantic and node' do let(:source) do ':foo and :bar' end it { expect(and_node.semantic_operator?).to be(true) } end end describe '#operator' do context 'with a logical and node' do let(:source) do ':foo && :bar' end it { expect(and_node.operator).to eq('&&') } end context 'with a semantic and node' do let(:source) do ':foo and :bar' end it { expect(and_node.operator).to eq('and') } end end describe '#alternate_operator' do context 'with a logical and node' do let(:source) do ':foo && :bar' end it { expect(and_node.alternate_operator).to eq('and') } end context 'with a semantic and node' do let(:source) do ':foo and :bar' end it { expect(and_node.alternate_operator).to eq('&&') } end end describe '#inverse_operator' do context 'with a logical and node' do let(:source) do ':foo && :bar' end it { expect(and_node.inverse_operator).to eq('||') } end context 'with a semantic and node' do let(:source) do ':foo and :bar' end it { expect(and_node.inverse_operator).to eq('or') } end end describe '#lhs' do context 'with a logical and node' do let(:source) do ':foo && 42' end it { expect(and_node.lhs.sym_type?).to be(true) } end context 'with a semantic and node' do let(:source) do ':foo and 42' end it { expect(and_node.lhs.sym_type?).to be(true) } end end describe '#rhs' do context 'with a logical and node' do let(:source) do ':foo && 42' end it { expect(and_node.rhs.int_type?).to be(true) } end context 'with a semantic and node' do let(:source) do ':foo and 42' end it { expect(and_node.rhs.int_type?).to be(true) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/args_node_spec.rb000066400000000000000000000035331373365104000224530ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ArgsNode do let(:args_node) { parse_source(source).ast.arguments } describe '.new' do context 'with a method definition' do let(:source) { 'def foo(x) end' } it { expect(args_node.is_a?(described_class)).to be(true) } end context 'with a block' do let(:source) { 'foo { |x| bar }' } it { expect(args_node.is_a?(described_class)).to be(true) } end context 'with a lambda literal' do let(:source) { '-> (x) { bar }' } it { expect(args_node.is_a?(described_class)).to be(true) } end end describe '#empty_and_without_delimiters?' do subject { args_node.empty_and_without_delimiters? } context 'with empty arguments' do context 'with a method definition' do let(:source) { 'def x; end' } it { is_expected.to be(true) } end context 'with a block' do let(:source) { 'x { }' } it { is_expected.to be(true) } end context 'with a lambda literal' do let(:source) { '-> { }' } it { is_expected.to be(true) } end end context 'with delimiters' do context 'with a method definition' do let(:source) { 'def x(); end' } it { is_expected.to be(false) } end context 'with a block' do let(:source) { 'x { || }' } it { is_expected.to be(false) } end context 'with a lambda literal' do let(:source) { '-> () { }' } it { is_expected.to be(false) } end end context 'with arguments' do context 'with a method definition' do let(:source) { 'def x a; end' } it { is_expected.to be(false) } end context 'with a lambda literal' do let(:source) { '-> a { }' } it { is_expected.to be(false) } end end end end rubocop-ast-0.6.0/spec/rubocop/ast/array_node_spec.rb000066400000000000000000000057071373365104000226420ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ArrayNode do let(:array_node) { parse_source(source).ast } describe '.new' do let(:source) { '[]' } it { expect(array_node.is_a?(described_class)).to be(true) } end describe '#values' do context 'with an empty array' do let(:source) { '[]' } it { expect(array_node.values.empty?).to be(true) } end context 'with an array of literals' do let(:source) { '[1, 2, 3]' } it { expect(array_node.values.size).to eq(3) } it { expect(array_node.values).to all(be_literal) } end context 'with an array of variables' do let(:source) { '[foo, bar]' } it { expect(array_node.values.size).to eq(2) } it { expect(array_node.values).to all(be_send_type) } end end describe '#each_value' do let(:source) { '[1, 2, 3]' } context 'with block' do it { expect(array_node.each_value {}.is_a?(described_class)).to be(true) } it do ret = [] array_node.each_value { |i| ret << i.to_s } expect(ret).to eq(['(int 1)', '(int 2)', '(int 3)']) end end context 'without block' do it { expect(array_node.each_value.is_a?(Enumerator)).to be(true) } end end describe '#square_brackets?' do context 'with square brackets' do let(:source) { '[1, 2, 3]' } it { expect(array_node.square_brackets?).to be_truthy } end context 'with a percent literal' do let(:source) { '%w(foo bar)' } it { expect(array_node.square_brackets?).to be_falsey } end end describe '#percent_literal?' do context 'with square brackets' do let(:source) { '[1, 2, 3]' } it { expect(array_node.percent_literal?).to be_falsey } it { expect(array_node.percent_literal?(:string)).to be_falsey } it { expect(array_node.percent_literal?(:symbol)).to be_falsey } end context 'with a string percent literal' do let(:source) { '%w(foo bar)' } it { expect(array_node.percent_literal?).to be_truthy } it { expect(array_node.percent_literal?(:string)).to be_truthy } it { expect(array_node.percent_literal?(:symbol)).to be_falsey } end context 'with a symbol percent literal' do let(:source) { '%i(foo bar)' } it { expect(array_node.percent_literal?).to be_truthy } it { expect(array_node.percent_literal?(:string)).to be_falsey } it { expect(array_node.percent_literal?(:symbol)).to be_truthy } end end describe '#bracketed?' do context 'with square brackets' do let(:source) { '[1, 2, 3]' } it { expect(array_node.bracketed?).to be(true) } end context 'with a percent literal' do let(:source) { '%w(foo bar)' } it { expect(array_node.bracketed?).to be(true) } end context 'unbracketed' do let(:array_node) do parse_source('foo = 1, 2, 3').ast.to_a.last end it { expect(array_node.bracketed?).to be(nil) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/block_node_spec.rb000066400000000000000000000131411373365104000226050ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::BlockNode do let(:block_node) { parse_source(source).ast } describe '.new' do let(:source) { 'foo { |q| bar(q) }' } it { expect(block_node.is_a?(described_class)).to be(true) } end describe '#arguments' do context 'with no arguments' do let(:source) { 'foo { bar }' } it { expect(block_node.arguments.empty?).to be(true) } end context 'with a single literal argument' do let(:source) { 'foo { |q| bar(q) }' } it { expect(block_node.arguments.size).to eq(1) } end context 'with a single splat argument' do let(:source) { 'foo { |*q| bar(q) }' } it { expect(block_node.arguments.size).to eq(1) } end context 'with multiple mixed arguments' do let(:source) { 'foo { |q, *z| bar(q, z) }' } it { expect(block_node.arguments.size).to eq(2) } end context '>= Ruby 2.7', :ruby27 do context 'using numbered parameters' do let(:source) { 'foo { _1 }' } it { expect(block_node.arguments.empty?).to be(true) } end end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'foo { bar }' } it { expect(block_node.arguments?).to be_falsey } end context 'with a single argument' do let(:source) { 'foo { |q| bar(q) }' } it { expect(block_node.arguments?).to be_truthy } end context 'with a single splat argument' do let(:source) { 'foo { |*q| bar(q) }' } it { expect(block_node.arguments?).to be_truthy } end context 'with multiple mixed arguments' do let(:source) { 'foo { |q, *z| bar(q, z) }' } it { expect(block_node.arguments?).to be_truthy } end context '>= Ruby 2.7', :ruby27 do context 'using numbered parameters' do let(:source) { 'foo { _1 }' } it { expect(block_node.arguments?).to be false } end end end describe '#braces?' do context 'when enclosed in braces' do let(:source) { 'foo { bar }' } it { expect(block_node.braces?).to be_truthy } end context 'when enclosed in do-end keywords' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { expect(block_node.braces?).to be_falsey } end end describe '#keywords?' do context 'when enclosed in braces' do let(:source) { 'foo { bar }' } it { expect(block_node.keywords?).to be_falsey } end context 'when enclosed in do-end keywords' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { expect(block_node.keywords?).to be_truthy } end end describe '#lambda?' do context 'when block belongs to a stabby lambda' do let(:source) { '-> { bar }' } it { expect(block_node.lambda?).to be_truthy } end context 'when block belongs to a method lambda' do let(:source) { 'lambda { bar }' } it { expect(block_node.lambda?).to be_truthy } end context 'when block belongs to a non-lambda method' do let(:source) { 'foo { bar }' } it { expect(block_node.lambda?).to be_falsey } end end describe '#delimiters' do context 'when enclosed in braces' do let(:source) { 'foo { bar }' } it { expect(block_node.delimiters).to eq(%w[{ }]) } end context 'when enclosed in do-end keywords' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { expect(block_node.delimiters).to eq(%w[do end]) } end end describe '#opening_delimiter' do context 'when enclosed in braces' do let(:source) { 'foo { bar }' } it { expect(block_node.opening_delimiter).to eq('{') } end context 'when enclosed in do-end keywords' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { expect(block_node.opening_delimiter).to eq('do') } end end describe '#closing_delimiter' do context 'when enclosed in braces' do let(:source) { 'foo { bar }' } it { expect(block_node.closing_delimiter).to eq('}') } end context 'when enclosed in do-end keywords' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { expect(block_node.closing_delimiter).to eq('end') } end end describe '#single_line?' do context 'when block is on a single line' do let(:source) { 'foo { bar }' } it { expect(block_node.single_line?).to be_truthy } end context 'when block is on several lines' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { expect(block_node.single_line?).to be_falsey } end end describe '#multiline?' do context 'when block is on a single line' do let(:source) { 'foo { bar }' } it { expect(block_node.multiline?).to be_falsey } end context 'when block is on several lines' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { expect(block_node.multiline?).to be_truthy } end end describe '#void_context?' do context 'when block method is each' do let(:source) { 'each { bar }' } it { expect(block_node.void_context?).to be_truthy } end context 'when block method is tap' do let(:source) { 'tap { bar }' } it { expect(block_node.void_context?).to be_truthy } end context 'when block method is not each' do let(:source) { 'map { bar }' } it { expect(block_node.void_context?).to be_falsey } end end end rubocop-ast-0.6.0/spec/rubocop/ast/break_node_spec.rb000066400000000000000000000002541373365104000226000ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'wrapped_arguments_node' RSpec.describe RuboCop::AST::BreakNode do it_behaves_like 'wrapped arguments node', 'break' end rubocop-ast-0.6.0/spec/rubocop/ast/case_match_node_spec.rb000066400000000000000000000053671373365104000236150ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::CaseMatchNode do let(:case_match_node) { parse_source(source).ast } context 'when using Ruby 2.7 or newer', :ruby27 do describe '.new' do let(:source) do <<~RUBY case expr in pattern end RUBY end it { expect(case_match_node.is_a?(described_class)).to be(true) } end describe '#keyword' do let(:source) do <<~RUBY case expr in pattern end RUBY end it { expect(case_match_node.keyword).to eq('case') } end describe '#in_pattern_branches' do let(:source) do <<~RUBY case expr in pattern in pattern in pattern end RUBY end it { expect(case_match_node.in_pattern_branches.size).to eq(3) } it { expect(case_match_node.in_pattern_branches).to all(be_in_pattern_type) } end describe '#each_in_pattern' do let(:source) do <<~RUBY case expr in pattern in pattern in pattern end RUBY end context 'when not passed a block' do it { expect(case_match_node.each_in_pattern.is_a?(Enumerator)).to be(true) } end context 'when passed a block' do it 'yields all the conditions' do expect { |b| case_match_node.each_in_pattern(&b) } .to yield_successive_args(*case_match_node.in_pattern_branches) end end end describe '#else?' do context 'without an else statement' do let(:source) do <<~RUBY case expr in pattern end RUBY end it { expect(case_match_node.else?).to be(false) } end context 'with an else statement' do let(:source) do <<~RUBY case expr in pattern else end RUBY end it { expect(case_match_node.else?).to be(true) } end end describe '#else_branch' do describe '#else?' do context 'without an else statement' do let(:source) do <<~RUBY case expr in pattern end RUBY end it { expect(case_match_node.else_branch.nil?).to be(true) } end context 'with an else statement' do let(:source) do <<~RUBY case expr in pattern else :foo end RUBY end it { expect(case_match_node.else_branch.sym_type?).to be(true) } end end end end end rubocop-ast-0.6.0/spec/rubocop/ast/case_node_spec.rb000066400000000000000000000073411373365104000224330ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::CaseNode do let(:ast) { parse_source(source).ast } let(:case_node) { ast } describe '.new' do let(:source) do ['case', 'when :foo then bar', 'end'].join("\n") end it { expect(case_node.is_a?(described_class)).to be(true) } end describe '#keyword' do let(:source) do ['case', 'when :foo then bar', 'end'].join("\n") end it { expect(case_node.keyword).to eq('case') } end describe '#when_branches' do let(:source) do ['case', 'when :foo then 1', 'when :bar then 2', 'when :baz then 3', 'end'].join("\n") end it { expect(case_node.when_branches.size).to eq(3) } it { expect(case_node.when_branches).to all(be_when_type) } end describe '#each_when' do let(:source) do ['case', 'when :foo then 1', 'when :bar then 2', 'when :baz then 3', 'end'].join("\n") end context 'when not passed a block' do it { expect(case_node.each_when.is_a?(Enumerator)).to be(true) } end context 'when passed a block' do it 'yields all the conditions' do expect { |b| case_node.each_when(&b) } .to yield_successive_args(*case_node.when_branches) end end end describe '#else?' do context 'without an else statement' do let(:source) do ['case', 'when :foo then :bar', 'end'].join("\n") end it { expect(case_node.else?).to be_falsey } end context 'with an else statement' do let(:source) do ['case', 'when :foo then :bar', 'else :baz', 'end'].join("\n") end it { expect(case_node.else?).to be_truthy } end end describe '#else_branch' do describe '#else?' do context 'without an else statement' do let(:source) do ['case', 'when :foo then :bar', 'end'].join("\n") end it { expect(case_node.else_branch.nil?).to be(true) } end context 'with an else statement' do let(:source) do ['case', 'when :foo then :bar', 'else :baz', 'end'].join("\n") end it { expect(case_node.else_branch.sym_type?).to be(true) } end end end describe '#branches' do context 'when there is an else' do let(:source) { <<~RUBY } case when :foo then # do nothing when :bar then 42 else 'hello' end RUBY it 'returns all the bodies' do expect(case_node.branches).to match [nil, be_int_type, be_str_type] end context 'with an empty else' do let(:source) { <<~RUBY } case when :foo then # do nothing when :bar then 42 else # do nothing end RUBY it 'returns all the bodies' do expect(case_node.branches).to match [nil, be_int_type, nil] end end end context 'when there is no else keyword' do let(:source) { <<~RUBY } case when :foo then # do nothing when :bar then 42 end RUBY it 'returns only then when bodies' do expect(case_node.branches).to match [nil, be_int_type] end end context 'when compared to an IfNode' do let(:source) { <<~RUBY } case when foo then 1 when bar then 2 else end if foo then 1 elsif bar then 2 else end RUBY let(:case_node) { ast.children.first } let(:if_node) { ast.children.last } it 'returns the same' do expect(case_node.branches).to eq if_node.branches end end end end rubocop-ast-0.6.0/spec/rubocop/ast/class_node_spec.rb000066400000000000000000000025261373365104000226250ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ClassNode do let(:class_node) { parse_source(source).ast } describe '.new' do let(:source) do 'class Foo; end' end it { expect(class_node.is_a?(described_class)).to be(true) } end describe '#identifier' do let(:source) do 'class Foo; end' end it { expect(class_node.identifier.const_type?).to be(true) } end describe '#parent_class' do context 'when a parent class is specified' do let(:source) do 'class Foo < Bar; end' end it { expect(class_node.parent_class.const_type?).to be(true) } end context 'when no parent class is specified' do let(:source) do 'class Foo; end' end it { expect(class_node.parent_class).to be(nil) } end end describe '#body' do context 'with a single expression body' do let(:source) do 'class Foo; bar; end' end it { expect(class_node.body.send_type?).to be(true) } end context 'with a multi-expression body' do let(:source) do 'class Foo; bar; baz; end' end it { expect(class_node.body.begin_type?).to be(true) } end context 'with an empty body' do let(:source) do 'class Foo; end' end it { expect(class_node.body).to be(nil) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/const_node_spec.rb000066400000000000000000000027501373365104000226450ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ConstNode do let(:ast) { parse_source(source).ast } let(:const_node) { ast } let(:source) { '::Foo::Bar::BAZ' } describe '#namespace' do it { expect(const_node.namespace.source).to eq '::Foo::Bar' } end describe '#short_name' do it { expect(const_node.short_name).to eq :BAZ } end describe '#module_name?' do it { expect(const_node.module_name?).to eq false } context 'with a constant with a lowercase letter' do let(:source) { '::Foo::Bar' } it { expect(const_node.module_name?).to eq true } end end describe '#absolute?' do it { expect(const_node.absolute?).to eq true } context 'with a constant not starting with ::' do let(:source) { 'Foo::Bar::BAZ' } it { expect(const_node.absolute?).to eq false } end context 'with a non-namespaced constant' do let(:source) { 'Foo' } it { expect(const_node.absolute?).to eq false } end end describe '#relative?' do context 'with a non-namespaced constant' do let(:source) { 'Foo' } it { expect(const_node.relative?).to eq true } end end describe '#each_path' do let(:source) { 'var = ::Foo::Bar::BAZ' } let(:const_node) { ast.children.last } it 'yields all parts of the namespace' do expect(const_node.each_path.map(&:type)).to eq %i[cbase const const] expect(const_node.each_path.to_a.last(2).map(&:short_name)).to eq %i[Foo Bar] end end end rubocop-ast-0.6.0/spec/rubocop/ast/def_node_spec.rb000066400000000000000000000316731373365104000222630ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::DefNode do let(:def_node) { parse_source(source).ast } describe '.new' do context 'with a def node' do let(:source) { 'def foo(bar); end' } it { expect(def_node.is_a?(described_class)).to be(true) } end context 'with a defs node' do let(:source) { 'def self.foo(bar); end' } it { expect(def_node.is_a?(described_class)).to be(true) } end end describe '#method_name' do context 'with a plain method' do let(:source) { 'def foo; end' } it { expect(def_node.method_name).to eq(:foo) } end context 'with a setter method' do let(:source) { 'def foo=(bar); end' } it { expect(def_node.method_name).to eq(:foo=) } end context 'with an operator method' do let(:source) { 'def ==(bar); end' } it { expect(def_node.method_name).to eq(:==) } end context 'with a unary method' do let(:source) { 'def -@; end' } it { expect(def_node.method_name).to eq(:-@) } end end describe '#method?' do context 'when message matches' do context 'when argument is a symbol' do let(:source) { 'bar(:baz)' } it { expect(def_node.method?(:bar)).to be_truthy } end context 'when argument is a string' do let(:source) { 'bar(:baz)' } it { expect(def_node.method?('bar')).to be_truthy } end end context 'when message does not match' do context 'when argument is a symbol' do let(:source) { 'bar(:baz)' } it { expect(def_node.method?(:foo)).to be_falsey } end context 'when argument is a string' do let(:source) { 'bar(:baz)' } it { expect(def_node.method?('foo')).to be_falsey } end end end describe '#arguments' do context 'with no arguments' do let(:source) { 'def foo; end' } it { expect(def_node.arguments.empty?).to be(true) } end context 'with a single regular argument' do let(:source) { 'def foo(bar); end' } it { expect(def_node.arguments.size).to eq(1) } end context 'with a single rest argument' do let(:source) { 'def foo(*baz); end' } it { expect(def_node.arguments.size).to eq(1) } end context 'with multiple regular arguments' do let(:source) { 'def foo(bar, baz); end' } it { expect(def_node.arguments.size).to eq(2) } end context 'with multiple mixed arguments' do let(:source) { 'def foo(bar, *baz); end' } it { expect(def_node.arguments.size).to eq(2) } end context 'with argument forwarding', :ruby27 do let(:source) { 'def foo(...); end' } it { expect(def_node.arguments.size).to eq(1) } end end describe '#first_argument' do context 'with no arguments' do let(:source) { 'def foo; end' } it { expect(def_node.first_argument.nil?).to be(true) } end context 'with a single regular argument' do let(:source) { 'def foo(bar); end' } it { expect(def_node.first_argument.arg_type?).to be(true) } end context 'with a single rest argument' do let(:source) { 'def foo(*bar); end' } it { expect(def_node.first_argument.restarg_type?).to be(true) } end context 'with a single keyword argument' do let(:source) { 'def foo(bar: :baz); end' } it { expect(def_node.first_argument.kwoptarg_type?).to be(true) } end context 'with multiple regular arguments' do let(:source) { 'def foo(bar, baz); end' } it { expect(def_node.first_argument.arg_type?).to be(true) } end context 'with multiple mixed arguments' do let(:source) { 'def foo(bar, *baz); end' } it { expect(def_node.first_argument.arg_type?).to be(true) } end end describe '#last_argument' do context 'with no arguments' do let(:source) { 'def foo; end' } it { expect(def_node.last_argument.nil?).to be(true) } end context 'with a single regular argument' do let(:source) { 'def foo(bar); end' } it { expect(def_node.last_argument.arg_type?).to be(true) } end context 'with a single rest argument' do let(:source) { 'def foo(*bar); end' } it { expect(def_node.last_argument.restarg_type?).to be(true) } end context 'with a single keyword argument' do let(:source) { 'def foo(bar: :baz); end' } it { expect(def_node.last_argument.kwoptarg_type?).to be(true) } end context 'with multiple regular arguments' do let(:source) { 'def foo(bar, baz); end' } it { expect(def_node.last_argument.arg_type?).to be(true) } end context 'with multiple mixed arguments' do let(:source) { 'def foo(bar, *baz); end' } it { expect(def_node.last_argument.restarg_type?).to be(true) } end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'def foo; end' } it { expect(def_node.arguments?).to be_falsey } end context 'with a single regular argument' do let(:source) { 'def foo(bar); end' } it { expect(def_node.arguments?).to be_truthy } end context 'with a single rest argument' do let(:source) { 'def foo(*bar); end' } it { expect(def_node.arguments?).to be_truthy } end context 'with a single keyword argument' do let(:source) { 'def foo(bar: :baz); end' } it { expect(def_node.arguments?).to be_truthy } end context 'with multiple regular arguments' do let(:source) { 'def foo(bar, baz); end' } it { expect(def_node.arguments?).to be_truthy } end context 'with multiple mixed arguments' do let(:source) { 'def foo(bar, *baz); end' } it { expect(def_node.arguments?).to be_truthy } end end describe '#rest_argument?' do context 'with a rest argument' do let(:source) { 'def foo(*bar); end' } it { expect(def_node.rest_argument?).to be_truthy } end context 'with no arguments' do let(:source) { 'def foo; end' } it { expect(def_node.rest_argument?).to be_falsey } end context 'with regular arguments' do let(:source) { 'def foo(bar); end' } it { expect(def_node.rest_argument?).to be_falsey } end context 'with mixed arguments' do let(:source) { 'def foo(bar, *baz); end' } it { expect(def_node.rest_argument?).to be_truthy } end end describe '#operator_method?' do context 'with a binary operator method' do let(:source) { 'def ==(bar); end' } it { expect(def_node.operator_method?).to be_truthy } end context 'with a unary operator method' do let(:source) { 'def -@; end' } it { expect(def_node.operator_method?).to be_truthy } end context 'with a setter method' do let(:source) { 'def foo=(bar); end' } it { expect(def_node.operator_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { expect(def_node.operator_method?).to be_falsey } end end describe '#comparison_method?' do context 'with a comparison method' do let(:source) { 'def <=(bar); end' } it { expect(def_node.comparison_method?).to be_truthy } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { expect(def_node.comparison_method?).to be_falsey } end end describe '#assignment_method?' do context 'with an assignment method' do let(:source) { 'def foo=(bar); end' } it { expect(def_node.assignment_method?).to be_truthy } end context 'with a bracket assignment method' do let(:source) { 'def []=(bar); end' } it { expect(def_node.assignment_method?).to be_truthy } end context 'with a comparison method' do let(:source) { 'def ==(bar); end' } it { expect(def_node.assignment_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { expect(def_node.assignment_method?).to be_falsey } end end describe '#void_context?' do context 'with an initializer method' do let(:source) { 'def initialize(bar); end' } it { expect(def_node.void_context?).to be_truthy } end context 'with a regular assignment method' do let(:source) { 'def foo=(bar); end' } it { expect(def_node.void_context?).to be_truthy } end context 'with a bracket assignment method' do let(:source) { 'def []=(bar); end' } it { expect(def_node.void_context?).to be_truthy } end context 'with a comparison method' do let(:source) { 'def ==(bar); end' } it { expect(def_node.void_context?).to be_falsey } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { expect(def_node.void_context?).to be_falsey } end end context 'when using Ruby 2.7 or newer', :ruby27 do describe '#argument_forwarding?' do let(:source) { 'def foo(...); end' } it { expect(def_node.argument_forwarding?).to be_truthy } end end describe '#receiver' do context 'with an instance method definition' do let(:source) { 'def foo(bar); end' } it { expect(def_node.receiver.nil?).to be(true) } end context 'with a class method definition' do let(:source) { 'def self.foo(bar); end' } it { expect(def_node.receiver.self_type?).to be(true) } end context 'with a singleton method definition' do let(:source) { 'def Foo.bar(baz); end' } it { expect(def_node.receiver.const_type?).to be(true) } end end describe '#self_receiver?' do context 'with an instance method definition' do let(:source) { 'def foo(bar); end' } it { expect(def_node.self_receiver?).to be_falsey } end context 'with a class method definition' do let(:source) { 'def self.foo(bar); end' } it { expect(def_node.self_receiver?).to be_truthy } end context 'with a singleton method definition' do let(:source) { 'def Foo.bar(baz); end' } it { expect(def_node.self_receiver?).to be_falsey } end end describe '#const_receiver?' do context 'with an instance method definition' do let(:source) { 'def foo(bar); end' } it { expect(def_node.const_receiver?).to be_falsey } end context 'with a class method definition' do let(:source) { 'def self.foo(bar); end' } it { expect(def_node.const_receiver?).to be_falsey } end context 'with a singleton method definition' do let(:source) { 'def Foo.bar(baz); end' } it { expect(def_node.const_receiver?).to be_truthy } end end describe '#predicate_method?' do context 'with a predicate method' do let(:source) { 'def foo?(bar); end' } it { expect(def_node.predicate_method?).to be_truthy } end context 'with a bang method' do let(:source) { 'def foo!(bar); end' } it { expect(def_node.predicate_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { expect(def_node.predicate_method?).to be_falsey } end end describe '#bang_method?' do context 'with a bang method' do let(:source) { 'def foo!(bar); end' } it { expect(def_node.bang_method?).to be_truthy } end context 'with a predicate method' do let(:source) { 'def foo?(bar); end' } it { expect(def_node.bang_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { expect(def_node.bang_method?).to be_falsey } end end describe '#camel_case_method?' do context 'with a camel case method' do let(:source) { 'def Foo(bar); end' } it { expect(def_node.camel_case_method?).to be_truthy } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { expect(def_node.camel_case_method?).to be_falsey } end end describe '#block_argument?' do context 'with a block argument' do let(:source) { 'def foo(&bar); end' } it { expect(def_node.block_argument?).to be_truthy } end context 'with no arguments' do let(:source) { 'def foo; end' } it { expect(def_node.block_argument?).to be_falsey } end context 'with regular arguments' do let(:source) { 'def foo(bar); end' } it { expect(def_node.block_argument?).to be_falsey } end context 'with mixed arguments' do let(:source) { 'def foo(bar, &baz); end' } it { expect(def_node.block_argument?).to be_truthy } end end describe '#body' do context 'with no body' do let(:source) { 'def foo(bar); end' } it { expect(def_node.body.nil?).to be(true) } end context 'with a single expression body' do let(:source) { 'def foo(bar); baz; end' } it { expect(def_node.body.send_type?).to be(true) } end context 'with a multi-expression body' do let(:source) { 'def foo(bar); baz; qux; end' } it { expect(def_node.body.begin_type?).to be(true) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/defined_node_spec.rb000066400000000000000000000013701373365104000231120ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::DefinedNode do let(:defined_node) { parse_source(source).ast } describe '.new' do context 'with a defined? node' do let(:source) { 'defined? :foo' } it { expect(defined_node.is_a?(described_class)).to be(true) } end end describe '#receiver' do let(:source) { 'defined? :foo' } it { expect(defined_node.receiver).to eq(nil) } end describe '#method_name' do let(:source) { 'defined? :foo' } it { expect(defined_node.method_name).to eq(:defined?) } end describe '#arguments' do let(:source) { 'defined? :foo' } it { expect(defined_node.arguments.size).to eq(1) } it { expect(defined_node.arguments).to all(be_sym_type) } end end rubocop-ast-0.6.0/spec/rubocop/ast/ensure_node_spec.rb000066400000000000000000000007101373365104000230120ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::EnsureNode do let(:ensure_node) { parse_source(source).ast.children.first } describe '.new' do let(:source) { 'begin; beginbody; ensure; ensurebody; end' } it { expect(ensure_node.is_a?(described_class)).to be(true) } end describe '#body' do let(:source) { 'begin; beginbody; ensure; :ensurebody; end' } it { expect(ensure_node.body.sym_type?).to be(true) } end end rubocop-ast-0.6.0/spec/rubocop/ast/ext/000077500000000000000000000000001373365104000177475ustar00rootroot00000000000000rubocop-ast-0.6.0/spec/rubocop/ast/ext/range_spec.rb000066400000000000000000000007261373365104000224070ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::Ext::Range do let(:source) { <<~RUBY } [ 1, 2 ] RUBY let(:node) { parse_source(source).ast } describe '#line_span' do it 'returns the range of lines a range occupies' do expect(node.loc.begin.line_span).to eq 1..1 end it 'accepts an `exclude_end` keyword argument' do expect(node.loc.expression.line_span(exclude_end: true)).to eq 1...4 end end end rubocop-ast-0.6.0/spec/rubocop/ast/ext/set_spec.rb000066400000000000000000000004001373365104000220730ustar00rootroot00000000000000# frozen_string_literal: true # rubocop:disable RSpec/DescribeClass, Style/CaseEquality RSpec.describe 'Set#===' do it 'tests for inclusion' do expect(Set[1, 2, 3] === 2).to eq true end end # rubocop:enable RSpec/DescribeClass, Style/CaseEquality rubocop-ast-0.6.0/spec/rubocop/ast/float_node_spec.rb000066400000000000000000000012251373365104000226200ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::FloatNode do let(:float_node) { parse_source(source).ast } describe '.new' do let(:source) { '42.0' } it { expect(float_node.is_a?(described_class)).to be_truthy } end describe '#sign?' do context 'explicit positive float' do let(:source) { '+42.0' } it { expect(float_node.sign?).to be_truthy } end context 'explicit negative float' do let(:source) { '-42.0' } it { expect(float_node.sign?).to be_truthy } end end describe '#value' do let(:source) do '1.5' end it { expect(float_node.value).to eq(1.5) } end end rubocop-ast-0.6.0/spec/rubocop/ast/for_node_spec.rb000066400000000000000000000033611373365104000223040ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ForNode do let(:for_node) { parse_source(source).ast } describe '.new' do let(:source) { 'for foo in bar; baz; end' } it { expect(for_node.is_a?(described_class)).to be(true) } end describe '#keyword' do let(:source) { 'for foo in bar; baz; end' } it { expect(for_node.keyword).to eq('for') } end describe '#do?' do context 'with a do keyword' do let(:source) { 'for foo in bar do baz; end' } it { expect(for_node.do?).to be_truthy } end context 'without a do keyword' do let(:source) { 'for foo in bar; baz; end' } it { expect(for_node.do?).to be_falsey } end end describe '#void_context?' do context 'with a do keyword' do let(:source) { 'for foo in bar do baz; end' } it { expect(for_node.void_context?).to be_truthy } end context 'without a do keyword' do let(:source) { 'for foo in bar; baz; end' } it { expect(for_node.void_context?).to be_truthy } end end describe '#variable' do let(:source) { 'for foo in :bar; :baz; end' } it { expect(for_node.variable.lvasgn_type?).to be(true) } end describe '#collection' do let(:source) { 'for foo in :bar; baz; end' } it { expect(for_node.collection.sym_type?).to be(true) } end describe '#body' do let(:source) { 'for foo in bar; :baz; end' } it { expect(for_node.body.sym_type?).to be(true) } end describe '#post_condition_loop?' do let(:source) { 'for foo in bar; baz; end' } it { expect(for_node.post_condition_loop?).to be_falsey } end describe '#loop_keyword?' do let(:source) { 'for foo in bar; baz; end' } it { expect(for_node.loop_keyword?).to be_truthy } end end rubocop-ast-0.6.0/spec/rubocop/ast/forward_args_node_spec.rb000066400000000000000000000011601373365104000241710ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ForwardArgsNode do let(:args_node) { parse_source(source).ast.arguments } let(:source) { 'def foo(...); end' } context 'when using Ruby 2.7 or newer', :ruby27 do if RuboCop::AST::Builder.emit_forward_arg describe '#to_a' do it { expect(args_node.to_a).to contain_exactly(be_forward_arg_type) } end else describe '.new' do it { expect(args_node.is_a?(described_class)).to be(true) } end describe '#to_a' do it { expect(args_node.to_a).to contain_exactly(args_node) } end end end end rubocop-ast-0.6.0/spec/rubocop/ast/hash_node_spec.rb000066400000000000000000000131761373365104000224460ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::HashNode do let(:hash_node) { parse_source(source).ast } describe '.new' do let(:source) { '{}' } it { expect(hash_node.is_a?(described_class)).to be(true) } end describe '#pairs' do context 'with an empty hash' do let(:source) { '{}' } it { expect(hash_node.pairs.empty?).to be(true) } end context 'with a hash of literals' do let(:source) { '{ a: 1, b: 2, c: 3 }' } it { expect(hash_node.pairs.size).to eq(3) } it { expect(hash_node.pairs).to all(be_pair_type) } end context 'with a hash of variables' do let(:source) { '{ a: foo, b: bar }' } it { expect(hash_node.pairs.size).to eq(2) } it { expect(hash_node.pairs).to all(be_pair_type) } end end describe '#empty?' do context 'with an empty hash' do let(:source) { '{}' } it { expect(hash_node.empty?).to be(true) } end context 'with a hash containing pairs' do let(:source) { '{ a: 1, b: 2 }' } it { expect(hash_node.empty?).to be(false) } end context 'with a hash containing a keyword splat' do let(:source) { '{ **foo }' } it { expect(hash_node.empty?).to be(false) } end end describe '#keys' do context 'with an empty hash' do let(:source) { '{}' } it { expect(hash_node.keys.empty?).to be(true) } end context 'with a hash with symbol keys' do let(:source) { '{ a: 1, b: 2, c: 3 }' } it { expect(hash_node.keys.size).to eq(3) } it { expect(hash_node.keys).to all(be_sym_type) } end context 'with a hash with string keys' do let(:source) { "{ 'a' => foo,'b' => bar }" } it { expect(hash_node.keys.size).to eq(2) } it { expect(hash_node.keys).to all(be_str_type) } end end describe '#each_key' do let(:source) { '{ a: 1, b: 2, c: 3 }' } context 'when not passed a block' do it { expect(hash_node.each_key.is_a?(Enumerator)).to be(true) } end context 'when passed a block' do let(:expected) do [ hash_node.pairs[0].key, hash_node.pairs[1].key, hash_node.pairs[2].key ] end it 'yields all the pairs' do expect { |b| hash_node.each_key(&b) } .to yield_successive_args(*expected) end end end describe '#values' do context 'with an empty hash' do let(:source) { '{}' } it { expect(hash_node.values.empty?).to be(true) } end context 'with a hash with literal values' do let(:source) { '{ a: 1, b: 2, c: 3 }' } it { expect(hash_node.values.size).to eq(3) } it { expect(hash_node.values).to all(be_literal) } end context 'with a hash with string keys' do let(:source) { '{ a: foo, b: bar }' } it { expect(hash_node.values.size).to eq(2) } it { expect(hash_node.values).to all(be_send_type) } end end describe '#each_value' do let(:source) { '{ a: 1, b: 2, c: 3 }' } context 'when not passed a block' do it { expect(hash_node.each_value.is_a?(Enumerator)).to be(true) } end context 'when passed a block' do let(:expected) do [ hash_node.pairs[0].value, hash_node.pairs[1].value, hash_node.pairs[2].value ] end it 'yields all the pairs' do expect { |b| hash_node.each_value(&b) } .to yield_successive_args(*expected) end end end describe '#each_pair' do let(:source) { '{ a: 1, b: 2, c: 3 }' } context 'when not passed a block' do it { expect(hash_node.each_pair.is_a?(Enumerator)).to be(true) } end context 'when passed a block' do let(:expected) do hash_node.pairs.map(&:to_a) end it 'yields all the pairs' do expect { |b| hash_node.each_pair(&b) } .to yield_successive_args(*expected) end end end describe '#pairs_on_same_line?' do context 'with all pairs on the same line' do let(:source) { '{ a: 1, b: 2 }' } it { expect(hash_node.pairs_on_same_line?).to be_truthy } end context 'with no pairs on the same line' do let(:source) do ['{ a: 1,', ' b: 2 }'].join("\n") end it { expect(hash_node.pairs_on_same_line?).to be_falsey } end context 'with some pairs on the same line' do let(:source) do ['{ a: 1,', ' b: 2, c: 3 }'].join("\n") end it { expect(hash_node.pairs_on_same_line?).to be_truthy } end end describe '#mixed_delimiters?' do context 'when all pairs are using a colon delimiter' do let(:source) { '{ a: 1, b: 2 }' } it { expect(hash_node.mixed_delimiters?).to be_falsey } end context 'when all pairs are using a hash rocket delimiter' do let(:source) { '{ :a => 1, :b => 2 }' } it { expect(hash_node.mixed_delimiters?).to be_falsey } end context 'when pairs are using different delimiters' do let(:source) { '{ :a => 1, b: 2 }' } it { expect(hash_node.mixed_delimiters?).to be_truthy } end end describe '#braces?' do context 'with braces' do let(:source) { '{ a: 1, b: 2 }' } it { expect(hash_node.braces?).to be_truthy } end context 'as an argument with no braces' do let(:source) { 'foo(:bar, a: 1, b: 2)' } let(:hash_argument) { hash_node.children.last } it { expect(hash_argument.braces?).to be_falsey } end context 'as an argument with braces' do let(:source) { 'foo(:bar, { a: 1, b: 2 })' } let(:hash_argument) { hash_node.children.last } it { expect(hash_argument.braces?).to be_truthy } end end end rubocop-ast-0.6.0/spec/rubocop/ast/if_node_spec.rb000066400000000000000000000260351373365104000221170ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::IfNode do let(:if_node) { parse_source(source).ast } describe '.new' do context 'with a regular if statement' do let(:source) { 'if foo?; :bar; end' } it { expect(if_node.is_a?(described_class)).to be(true) } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { expect(if_node.is_a?(described_class)).to be(true) } end context 'with a modifier statement' do let(:source) { ':foo if bar?' } it { expect(if_node.is_a?(described_class)).to be(true) } end end describe '#keyword' do context 'with an if statement' do let(:source) { 'if foo?; :bar; end' } it { expect(if_node.keyword).to eq('if') } end context 'with an unless statement' do let(:source) { 'unless foo?; :bar; end' } it { expect(if_node.keyword).to eq('unless') } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { expect(if_node.keyword).to eq('') } end end describe '#inverse_keyword?' do context 'with an if statement' do let(:source) { 'if foo?; :bar; end' } it { expect(if_node.inverse_keyword).to eq('unless') } end context 'with an unless statement' do let(:source) { 'unless foo?; :bar; end' } it { expect(if_node.inverse_keyword).to eq('if') } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { expect(if_node.inverse_keyword).to eq('') } end end describe '#if?' do context 'with an if statement' do let(:source) { 'if foo?; :bar; end' } it { expect(if_node.if?).to be_truthy } end context 'with an unless statement' do let(:source) { 'unless foo?; :bar; end' } it { expect(if_node.if?).to be_falsey } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { expect(if_node.if?).to be_falsey } end end describe '#unless?' do context 'with an if statement' do let(:source) { 'if foo?; :bar; end' } it { expect(if_node.unless?).to be_falsey } end context 'with an unless statement' do let(:source) { 'unless foo?; :bar; end' } it { expect(if_node.unless?).to be_truthy } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { expect(if_node.unless?).to be_falsey } end end describe '#ternary?' do context 'with an if statement' do let(:source) { 'if foo?; :bar; end' } it { expect(if_node.ternary?).to be_falsey } end context 'with an unless statement' do let(:source) { 'unless foo?; :bar; end' } it { expect(if_node.ternary?).to be_falsey } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { expect(if_node.ternary?).to be_truthy } end end describe '#elsif?' do context 'with an elsif statement' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'end'].join("\n") end let(:elsif_node) { if_node.else_branch } it { expect(elsif_node.elsif?).to be_truthy } end context 'with an if statement comtaining an elsif' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'end'].join("\n") end it { expect(if_node.elsif?).to be_falsey } end context 'without an elsif statement' do let(:source) do ['if foo?', ' 1', 'end'].join("\n") end it { expect(if_node.elsif?).to be_falsey } end end describe '#else?' do context 'with an elsif statement' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'end'].join("\n") end # Note: This is a legacy behavior. it { expect(if_node.else?).to be_truthy } end context 'without an else statement' do let(:source) do ['if foo?', ' 1', 'else', ' 2', 'end'].join("\n") end it { expect(if_node.elsif?).to be_falsey } end end describe '#modifier_form?' do context 'with a non-modifier if statement' do let(:source) { 'if foo?; :bar; end' } it { expect(if_node.modifier_form?).to be_falsey } end context 'with a non-modifier unless statement' do let(:source) { 'unless foo?; :bar; end' } it { expect(if_node.modifier_form?).to be_falsey } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { expect(if_node.modifier_form?).to be_falsey } end context 'with a modifier if statement' do let(:source) { ':bar if foo?' } it { expect(if_node.modifier_form?).to be_truthy } end context 'with a modifier unless statement' do let(:source) { ':bar unless foo?' } it { expect(if_node.modifier_form?).to be_truthy } end end describe '#nested_conditional?' do context 'with no nested conditionals' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'else', ' 3', 'end'].join("\n") end it { expect(if_node.nested_conditional?).to be_falsey } end context 'with nested conditionals in if clause' do let(:source) do ['if foo?', ' if baz; 4; end', 'elsif bar?', ' 2', 'else', ' 3', 'end'].join("\n") end it { expect(if_node.nested_conditional?).to be_truthy } end context 'with nested conditionals in elsif clause' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' if baz; 4; end', 'else', ' 3', 'end'].join("\n") end it { expect(if_node.nested_conditional?).to be_truthy } end context 'with nested conditionals in else clause' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'else', ' if baz; 4; end', 'end'].join("\n") end it { expect(if_node.nested_conditional?).to be_truthy } end context 'with nested ternary operators' do context 'when nested in the truthy branch' do let(:source) { 'foo? ? bar? ? 1 : 2 : 3' } it { expect(if_node.nested_conditional?).to be_truthy } end context 'when nested in the falsey branch' do let(:source) { 'foo? ? 3 : bar? ? 1 : 2' } it { expect(if_node.nested_conditional?).to be_truthy } end end end describe '#elsif_conditional?' do context 'with one elsif conditional' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'else', ' 3', 'end'].join("\n") end it { expect(if_node.elsif_conditional?).to be_truthy } end context 'with multiple elsif conditionals' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'elsif baz?', ' 3', 'else', ' 4', 'end'].join("\n") end it { expect(if_node.elsif_conditional?).to be_truthy } end context 'with nested conditionals in if clause' do let(:source) do ['if foo?', ' if baz; 1; end', 'else', ' 2', 'end'].join("\n") end it { expect(if_node.elsif_conditional?).to be_falsey } end context 'with nested conditionals in else clause' do let(:source) do ['if foo?', ' 1', 'else', ' if baz; 2; end', 'end'].join("\n") end it { expect(if_node.elsif_conditional?).to be_falsey } end context 'with nested ternary operators' do context 'when nested in the truthy branch' do let(:source) { 'foo? ? bar? ? 1 : 2 : 3' } it { expect(if_node.elsif_conditional?).to be_falsey } end context 'when nested in the falsey branch' do let(:source) { 'foo? ? 3 : bar? ? 1 : 2' } it { expect(if_node.elsif_conditional?).to be_falsey } end end end describe '#if_branch' do context 'with an if statement' do let(:source) do ['if foo?', ' :foo', 'else', ' 42', 'end'].join("\n") end it { expect(if_node.if_branch.sym_type?).to be(true) } end context 'with an unless statement' do let(:source) do ['unless foo?', ' :foo', 'else', ' 42', 'end'].join("\n") end it { expect(if_node.if_branch.sym_type?).to be(true) } end context 'with a ternary operator' do let(:source) { 'foo? ? :foo : 42' } it { expect(if_node.if_branch.sym_type?).to be(true) } end end describe '#else_branch' do context 'with an if statement' do let(:source) do ['if foo?', ' :foo', 'else', ' 42', 'end'].join("\n") end it { expect(if_node.else_branch.int_type?).to be(true) } end context 'with an unless statement' do let(:source) do ['unless foo?', ' :foo', 'else', ' 42', 'end'].join("\n") end it { expect(if_node.else_branch.int_type?).to be(true) } end context 'with a ternary operator' do let(:source) { 'foo? ? :foo : 42' } it { expect(if_node.else_branch.int_type?).to be(true) } end end describe '#branches' do context 'with an if statement' do let(:source) { 'if foo?; :bar; end' } it { expect(if_node.branches.size).to eq(1) } it { expect(if_node.branches).to all(be_literal) } end context 'with an unless statement' do let(:source) { 'unless foo?; :bar; end' } it { expect(if_node.branches.size).to eq(1) } it { expect(if_node.branches).to all(be_literal) } end context 'with an else statement' do let(:source) do ['if foo?', ' 1', 'else', ' 2', 'end'].join("\n") end it { expect(if_node.branches.size).to eq(2) } it { expect(if_node.branches).to all(be_literal) } end context 'with an elsif statement' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'else', ' 3', 'end'].join("\n") end it { expect(if_node.branches.size).to eq(3) } it { expect(if_node.branches).to all(be_literal) } end end describe '#each_branch' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'else', ' 3', 'end'].join("\n") end context 'when not passed a block' do it { expect(if_node.each_branch.is_a?(Enumerator)).to be(true) } end context 'when passed a block' do it 'yields all the branches' do expect { |b| if_node.each_branch(&b) } .to yield_successive_args(*if_node.branches) end end end end rubocop-ast-0.6.0/spec/rubocop/ast/int_node_spec.rb000066400000000000000000000011751373365104000223110ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::IntNode do let(:int_node) { parse_source(source).ast } describe '.new' do let(:source) { '42' } it { expect(int_node.is_a?(described_class)).to be_truthy } end describe '#sign?' do context 'explicit positive int' do let(:source) { '+42' } it { expect(int_node.sign?).to be_truthy } end context 'explicit negative int' do let(:source) { '-42' } it { expect(int_node.sign?).to be_truthy } end end describe '#value' do let(:source) do '10' end it { expect(int_node.value).to eq(10) } end end rubocop-ast-0.6.0/spec/rubocop/ast/keyword_splat_node_spec.rb000066400000000000000000000214741373365104000244120ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::KeywordSplatNode do let(:kwsplat_node) { parse_source(source).ast.children.last } describe '.new' do let(:source) { '{ a: 1, **foo }' } it { expect(kwsplat_node.is_a?(described_class)).to be(true) } end describe '#hash_rocket?' do let(:source) { '{ a: 1, **foo }' } it { expect(kwsplat_node.hash_rocket?).to be_falsey } end describe '#colon?' do let(:source) { '{ a: 1, **foo }' } it { expect(kwsplat_node.colon?).to be_falsey } end describe '#key' do let(:source) { '{ a: 1, **foo }' } it { expect(kwsplat_node.key).to eq(kwsplat_node) } end describe '#value' do let(:source) { '{ a: 1, **foo }' } it { expect(kwsplat_node.value).to eq(kwsplat_node) } end describe '#operator' do let(:source) { '{ a: 1, **foo }' } it { expect(kwsplat_node.operator).to eq('**') } end describe '#same_line?' do let(:first_pair) { parse_source(source).ast.children[0] } let(:second_pair) { parse_source(source).ast.children[1] } context 'when both pairs are on the same line' do let(:source) do ['{', ' a: 1, **foo', '}'].join("\n") end it { expect(first_pair.same_line?(second_pair)).to be_truthy } end context 'when a multiline pair shares the same line' do let(:source) do ['{', ' a: (', ' ), **foo', '}'].join("\n") end it { expect(first_pair.same_line?(second_pair)).to be_truthy } it { expect(second_pair.same_line?(first_pair)).to be_truthy } end context 'when pairs are on separate lines' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.same_line?(second_pair)).to be_falsey } end end describe '#key_delta' do let(:pair_node) { parse_source(source).ast.children[0] } let(:kwsplat_node) { parse_source(source).ast.children[1] } context 'with alignment set to :left' do context 'when using colon delimiters' do context 'when keyword splat is aligned' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node)).to eq(0) } end context 'when keyword splat is ahead' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node)).to eq(2) } end context 'when keyword splat is behind' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node)).to eq(-2) } end context 'when keyword splat is on the same line' do let(:source) do ['{', ' a: 1, **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node)).to eq(0) } end end context 'when using hash rocket delimiters' do context 'when keyword splat is aligned' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node)).to eq(0) } end context 'when keyword splat is ahead' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node)).to eq(2) } end context 'when keyword splat is behind' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node)).to eq(-2) } end context 'when keyword splat is on the same line' do let(:source) do ['{', ' a => 1, **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node)).to eq(0) } end end end context 'with alignment set to :right' do context 'when using colon delimiters' do context 'when keyword splat is aligned' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } end context 'when keyword splat is ahead' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } end context 'when keyword splat is behind' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } end context 'when keyword splat is on the same line' do let(:source) do ['{', ' a: 1, **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } end end context 'when using hash rocket delimiters' do context 'when keyword splat is aligned' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } end context 'when keyword splat is ahead' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } end context 'when keyword splat is behind' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } end context 'when keyword splat is on the same line' do let(:source) do ['{', ' a => 1, **foo', '}'].join("\n") end it { expect(kwsplat_node.key_delta(pair_node, :right)).to eq(0) } end end end end describe '#value_delta' do let(:pair_node) { parse_source(source).ast.children[0] } let(:kwsplat_node) { parse_source(source).ast.children[1] } context 'when using colon delimiters' do context 'when keyword splat is left aligned' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } end context 'when keyword splat is ahead' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } end context 'when keyword splat is behind' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } end context 'when keyword splat is on the same line' do let(:source) do ['{', ' a: 1, **foo', '}'].join("\n") end it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } end end context 'when using hash rocket delimiters' do context 'when keyword splat is left aligned' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } end context 'when keyword splat is ahead' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } end context 'when keyword splat is behind' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } end context 'when keyword splat is on the same line' do let(:source) do ['{', ' a => 1, **foo', '}'].join("\n") end it { expect(kwsplat_node.value_delta(pair_node)).to eq(0) } end end end end rubocop-ast-0.6.0/spec/rubocop/ast/lambda_node_spec.rb000066400000000000000000000007721373365104000227410ustar00rootroot00000000000000# frozen_string_literal: true # Note: specs for `lambda?` and `lambda_literal?` in `send_node_spec` RSpec.describe RuboCop::AST::LambdaNode do subject(:lambda_node) { parse_source(source).ast } let(:source) { '->(a, b) { a + b }' } describe '#receiver' do it { expect(lambda_node.receiver).to eq nil } end describe '#method_name' do it { expect(lambda_node.method_name).to eq :lambda } end describe '#arguments' do it { expect(lambda_node.arguments.size).to eq 2 } end end rubocop-ast-0.6.0/spec/rubocop/ast/module_node_spec.rb000066400000000000000000000017201373365104000230000ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ModuleNode do let(:module_node) { parse_source(source).ast } describe '.new' do let(:source) do 'module Foo; end' end it { expect(module_node.is_a?(described_class)).to be(true) } end describe '#identifier' do let(:source) do 'module Foo; end' end it { expect(module_node.identifier.const_type?).to be(true) } end describe '#body' do context 'with a single expression body' do let(:source) do 'module Foo; bar; end' end it { expect(module_node.body.send_type?).to be(true) } end context 'with a multi-expression body' do let(:source) do 'module Foo; bar; baz; end' end it { expect(module_node.body.begin_type?).to be(true) } end context 'with an empty body' do let(:source) do 'module Foo; end' end it { expect(module_node.body).to be(nil) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/next_node_spec.rb000066400000000000000000000002521373365104000224700ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'wrapped_arguments_node' RSpec.describe RuboCop::AST::NextNode do it_behaves_like 'wrapped arguments node', 'next' end rubocop-ast-0.6.0/spec/rubocop/ast/node_pattern_spec.rb000066400000000000000000001624251373365104000232020ustar00rootroot00000000000000# frozen_string_literal: true require 'parser/current' RSpec.describe RuboCop::AST::NodePattern do include RuboCop::AST::Sexp def parse(code) buffer = Parser::Source::Buffer.new('(string)', 1, source: code) builder = RuboCop::AST::Builder.new Parser::CurrentRuby.new(builder).parse(buffer) end let(:root_node) { parse(ruby) } let(:node) { root_node } let(:params) { [] } let(:keyword_params) { {} } let(:instance) { described_class.new(pattern) } let(:method_name) { :match } let(:result) do if keyword_params.empty? # Avoid bug in Ruby < 2.6 instance.match(node, *params) else instance.match(node, *params, **keyword_params) end end RSpec::Matchers.define :match_code do |code, *params, **keyword_params| match do |pattern| instance = pattern.is_a?(String) ? described_class.new(pattern) : pattern code = parse(code) if code.is_a?(String) if keyword_params.empty? # Avoid bug in Ruby < 2.6 instance.public_send(method_name, code, *params) else instance.public_send(method_name, code, *params, **keyword_params) end end end RSpec::Matchers.define_negated_matcher :not_match_code, :match_code def match_codes(*codes) codes.map { |code| match_code(code) }.inject(:and) end def not_match_codes(*codes) codes.map { |code| not_match_code(code) }.inject(:and) end shared_examples 'nonmatching' do it "doesn't match" do expect(result).to be nil end end shared_examples 'invalid' do it 'is invalid' do expect { instance } .to raise_error(RuboCop::AST::NodePattern::Invalid) end end shared_examples 'single capture' do it 'yields captured value(s) and returns true if there is a block' do expect do |probe| compiled = instance retval = compiled.match(node, *params) do |capture| probe.to_proc.call(capture) :retval_from_block end expect(retval).to be :retval_from_block end.to yield_with_args(captured_val) end it 'returns captured values if there is no block' do retval = instance.match(node, *params) expect(retval).to eq captured_val end end shared_examples 'multiple capture' do it 'yields captured value(s) and returns true if there is a block' do expect do |probe| compiled = instance retval = compiled.match(node, *params) do |*captures| probe.to_proc.call(captures) :retval_from_block end expect(retval).to be :retval_from_block end.to yield_with_args(captured_vals) end it 'returns captured values if there is no block' do retval = instance.match(node, *params) expect(retval).to eq captured_vals end end describe 'bare node type' do let(:pattern) { 'send' } context 'on a node with the same type' do let(:ruby) { 'obj.method' } it { expect(pattern).to match_code(node) } end context 'on a node with a different type' do let(:ruby) { '@ivar' } it_behaves_like 'nonmatching' end context 'on a node with a matching, hyphenated type' do let(:pattern) { 'op-asgn' } let(:ruby) { 'a += 1' } # this is an (op-asgn ...) node it { expect(pattern).to match_code(node) } end describe '#pattern' do it 'returns the pattern' do expect(instance.pattern).to eq pattern end end describe '#to_s' do it 'is instructive' do expect(instance.to_s).to include pattern end end describe 'marshal compatibility' do let(:instance) { Marshal.load(Marshal.dump(super())) } let(:ruby) { 'obj.method' } it { expect(pattern).to match_code(node) } end describe '#dup' do let(:instance) { super().dup } let(:ruby) { 'obj.method' } it { expect(pattern).to match_code(node) } end if RUBY_VERSION >= '2.6' describe 'yaml compatibility' do let(:instance) do YAML.safe_load(YAML.dump(super()), permitted_classes: [described_class]) end let(:ruby) { 'obj.method' } it { expect(pattern).to match_code(node) } end end describe '#==' do let(:pattern) { " (send 42 \n :to_s ) " } it 'returns true iff the patterns are similar' do expect(instance == instance.dup).to eq true expect(instance == 42).to eq false expect(instance == described_class.new('(send)')).to eq false expect(instance == described_class.new('(send 42 :to_s)')).to eq true end end end describe 'node type' do describe 'in seq head' do let(:pattern) { '(send ...)' } context 'on a node with the same type' do let(:ruby) { '@ivar + 2' } it { expect(pattern).to match_code(node) } end context 'on a child with a different type' do let(:ruby) { '@ivar += 2' } it_behaves_like 'nonmatching' end end describe 'for a child' do let(:pattern) { '(_ send ...)' } context 'on a child with the same type' do let(:ruby) { 'foo.bar' } it { expect(pattern).to match_code(node) } end context 'on a child with a different type' do let(:ruby) { '@ivar.bar' } it_behaves_like 'nonmatching' end context 'on a child litteral' do let(:pattern) { '(_ _ send)' } let(:ruby) { '42.bar' } it_behaves_like 'nonmatching' end end end describe 'literals' do context 'negative integer literals' do let(:pattern) { '(int -100)' } let(:ruby) { '-100' } it { expect(pattern).to match_code(node) } end context 'positive float literals' do let(:pattern) { '(float 1.0)' } let(:ruby) { '1.0' } it { expect(pattern).to match_code(node) } end context 'negative float literals' do let(:pattern) { '(float -2.5)' } let(:ruby) { '-2.5' } it { expect(pattern).to match_code(node) } end context 'single quoted string literals' do let(:pattern) { '(str "foo")' } let(:ruby) { '"foo"' } it { expect(pattern).to match_code(node) } end context 'double quoted string literals' do let(:pattern) { '(str "foo")' } let(:ruby) { "'foo'" } it { expect(pattern).to match_code(node) } end context 'symbol literals' do let(:pattern) { '(sym :foo)' } let(:ruby) { ':foo' } it { expect(pattern).to match_code(node) } end describe 'bare literal' do let(:ruby) { ':bar' } let(:pattern) { ':bar' } context 'on a node' do it_behaves_like 'nonmatching' end context 'on a matching literal' do let(:node) { root_node.children[0] } it { expect(pattern).to match_code(node) } end end end describe 'nil' do context 'nil literals' do let(:pattern) { '(nil)' } let(:ruby) { 'nil' } it { expect(pattern).to match_code(node) } end context 'nil value in AST' do let(:pattern) { '(send nil :foo)' } let(:ruby) { 'foo' } it_behaves_like 'nonmatching' end context 'nil value in AST, use nil? method' do let(:pattern) { '(send nil? :foo)' } let(:ruby) { 'foo' } it { expect(pattern).to match_code(node) } end context 'against a node pattern (bug #5470)' do let(:pattern) { '(:send (:const ...) ...)' } let(:ruby) { 'foo' } it_behaves_like 'nonmatching' end end describe 'simple sequence' do let(:pattern) { '(send int :+ int)' } context 'on a node with the same type and matching children' do let(:ruby) { '1 + 1' } it { expect(pattern).to match_code(node) } end context 'on a node with a different type' do let(:ruby) { 'a = 1' } it_behaves_like 'nonmatching' end context 'on a node with the same type and non-matching children' do context 'with non-matching selector' do let(:ruby) { '1 - 1' } it_behaves_like 'nonmatching' end context 'with non-matching receiver type' do let(:ruby) { '1.0 + 1' } it_behaves_like 'nonmatching' end end context 'on a node with too many children' do let(:pattern) { '(send int :blah int)' } let(:ruby) { '1.blah(1, 2)' } it_behaves_like 'nonmatching' end context 'with a nested sequence in head position' do let(:pattern) { '((send) int :blah)' } it_behaves_like 'invalid' end context 'with a nested sequence in non-head position' do let(:pattern) { '(send (send _ :a) :b)' } let(:ruby) { 'obj.a.b' } it { expect(pattern).to match_code(node) } end end describe 'sequence with trailing ...' do let(:pattern) { '(send int :blah ...)' } context 'on a node with the same type and exact number of children' do let(:ruby) { '1.blah' } it { expect(pattern).to match_code(node) } end context 'on a node with the same type and more children' do context 'with 1 child more' do let(:ruby) { '1.blah(1)' } it { expect(pattern).to match_code(node) } end context 'with 2 children more' do let(:ruby) { '1.blah(1, :something)' } it { expect(pattern).to match_code(node) } end end context 'on a node with the same type and fewer children' do let(:pattern) { '(send int :blah int int ...)' } let(:ruby) { '1.blah(2)' } it_behaves_like 'nonmatching' end context 'on a node with fewer children, with a wildcard preceding' do let(:pattern) { '(hash _ ...)' } let(:ruby) { '{}' } it_behaves_like 'nonmatching' end context 'on a node with a different type' do let(:ruby) { 'A = 1' } it_behaves_like 'nonmatching' end context 'on a node with non-matching children' do let(:ruby) { '1.foo' } it_behaves_like 'nonmatching' end end describe 'wildcards' do describe 'unnamed wildcards' do context 'at the root level' do let(:pattern) { '_' } let(:ruby) { 'class << self; def something; 1; end end.freeze' } it { expect(pattern).to match_code(node) } end context 'within a sequence' do let(:pattern) { '(const _ _)' } let(:ruby) { 'Const' } it { expect(pattern).to match_code(node) } end context 'within a sequence with other patterns intervening' do let(:pattern) { '(ivasgn _ (int _))' } let(:ruby) { '@abc = 22' } it { expect(pattern).to match_code(node) } end context 'in head position of a sequence' do let(:pattern) { '(_ int ...)' } let(:ruby) { '1 + a' } it { expect(pattern).to match_code(node) } end context 'negated' do let(:pattern) { '!_' } let(:ruby) { '123' } it_behaves_like 'nonmatching' end end describe 'named wildcards' do # unification is done on named wildcards! context 'at the root level' do let(:pattern) { '_node' } let(:ruby) { 'class << self; def something; 1; end end.freeze' } it { expect(pattern).to match_code(node) } end context 'within a sequence' do context 'with values which can be unified' do let(:pattern) { '(send _num :+ _num)' } let(:ruby) { '5 + 5' } it { expect(pattern).to match_code(node) } end context 'with values which cannot be unified' do let(:pattern) { '(send _num :+ _num)' } let(:ruby) { '5 + 4' } it_behaves_like 'nonmatching' end context 'unifying the node type with an argument' do let(:pattern) { '(_type _ _type)' } let(:ruby) { 'obj.send' } it { expect(pattern).to match_code(node) } end end context 'within a sequence with other patterns intervening' do let(:pattern) { '(ivasgn _ivar (int _val))' } let(:ruby) { '@abc = 22' } it { expect(pattern).to match_code(node) } end context 'in head position of a sequence' do let(:pattern) { '(_type int ...)' } let(:ruby) { '1 + a' } it { expect(pattern).to match_code(node) } end context 'within a union' do context 'confined to the union' do context 'without unification' do let(:pattern) { '{(array (int 1) _num) (array _num (int 1))}' } let(:ruby) { '[2, 1]' } it { expect(pattern).to match_code(node) } end context 'with partial unification' do let(:pattern) { '{(array _num _num) (array _num (int 1))}' } context 'matching the unified branch' do let(:ruby) { '[5, 5]' } it { expect(pattern).to match_code(node) } end context 'matching the free branch' do let(:ruby) { '[2, 1]' } it { expect(pattern).to match_code(node) } end context 'that can not be unified' do let(:ruby) { '[3, 2]' } it_behaves_like 'nonmatching' end end end context 'with a preceding unifying constraint' do let(:pattern) do '(array _num {(array (int 1) _num) send (array _num (int 1))})' end context 'matching a branch' do let(:ruby) { '[2, [2, 1]]' } it { expect(pattern).to match_code(node) } end context 'that can not be unified' do let(:ruby) { '[3, [2, 1]]' } it_behaves_like 'nonmatching' end end context 'with a succeeding unifying constraint' do context 'with branches without the wildcard' do context 'encountered first' do let(:pattern) do '(array {send (array (int 1) _num) } _num)' end it_behaves_like 'invalid' end context 'encountered after' do let(:pattern) do '(array {(array (int 1) _num) (array _num (int 1)) send } _num)' end it_behaves_like 'invalid' end end context 'with all branches with the wildcard' do let(:pattern) do '(array {(array (int 1) _num) (array _num (int 1)) } _num)' end context 'matching the first branch' do let(:ruby) { '[[1, 2], 2]' } it { expect(pattern).to match_code(node) } end context 'matching another branch' do let(:ruby) { '[[2, 1], 2]' } it { expect(pattern).to match_code(node) } end context 'that can not be unified' do let(:ruby) { '[[2, 1], 1]' } it_behaves_like 'nonmatching' end end end end end end describe 'unions' do context 'at the top level' do context 'containing symbol literals' do context 'when the AST has a matching symbol' do let(:pattern) { '(send _ {:a :b})' } let(:ruby) { 'obj.b' } it { expect(pattern).to match_code(node) } end context 'when the AST does not have a matching symbol' do let(:pattern) { '(send _ {:a :b})' } let(:ruby) { 'obj.c' } it_behaves_like 'nonmatching' end end context 'containing string literals' do let(:pattern) { '(send (str {"a" "b"}) :upcase)' } let(:ruby) { '"a".upcase' } it { expect(pattern).to match_code(node) } end context 'containing integer literals' do let(:pattern) { '(send (int {1 10}) :abs)' } let(:ruby) { '10.abs' } it { expect(pattern).to match_code(node) } end context 'containing mixed node and literals' do let(:pattern) { '(send {int nil?} ...)' } let(:ruby) { 'obj' } it { expect(pattern).to match_code(node) } end context 'containing multiple []' do let(:pattern) { '{[(int odd?) int] [!nil float]}' } context 'on a node which meets all requirements of the first []' do let(:ruby) { '3' } it { expect(pattern).to match_code(node) } end context 'on a node which meets all requirements of the second []' do let(:ruby) { '2.4' } it { expect(pattern).to match_code(node) } end context 'on a node which meets some requirements but not all' do let(:ruby) { '2' } it_behaves_like 'nonmatching' end end end context 'nested inside a sequence' do let(:pattern) { '(send {const int} ...)' } let(:ruby) { 'Const.method' } it { expect(pattern).to match_code(node) } end context 'with a nested sequence' do let(:pattern) { '{(send int ...) (send const ...)}' } let(:ruby) { 'Const.method' } it { expect(pattern).to match_code(node) } end end describe 'captures on a wildcard' do context 'at the root level' do let(:pattern) { '$_' } let(:ruby) { 'begin; raise StandardError; rescue Exception => e; end' } let(:captured_val) { node } it_behaves_like 'single capture' end context 'in head position in a sequence' do let(:pattern) { '($_ ...)' } let(:ruby) { 'A.method' } let(:captured_val) { :send } it_behaves_like 'single capture' end context 'in head position in a sequence against nil (bug #5470)' do let(:pattern) { '($_ ...)' } let(:ruby) { '' } it_behaves_like 'nonmatching' end context 'in head position in a sequence against literal (bug #5470)' do let(:pattern) { '(int ($_ ...))' } let(:ruby) { '42' } it_behaves_like 'nonmatching' end context 'in non-head position in a sequence' do let(:pattern) { '(send $_ ...)' } let(:ruby) { 'A.method' } let(:captured_val) { s(:const, nil, :A) } it_behaves_like 'single capture' end context 'in a nested sequence' do let(:pattern) { '(send (const nil? $_) ...)' } let(:ruby) { 'A.method' } let(:captured_val) { :A } it_behaves_like 'single capture' end context 'nested in any child' do let(:pattern) { '(send $<(const nil? $_) $...>)' } let(:ruby) { 'A.method' } let(:captured_vals) { [[s(:const, nil, :A), :method], :A, [:method]] } it_behaves_like 'multiple capture' end end describe 'captures which also perform a match' do context 'on a sequence' do let(:pattern) { '(send $(send _ :keys) :each)' } let(:ruby) { '{}.keys.each' } let(:captured_val) { s(:send, s(:hash), :keys) } it_behaves_like 'single capture' end context 'on a set' do let(:pattern) { '(send _ ${:inc :dec})' } let(:ruby) { '1.dec' } let(:captured_val) { :dec } it_behaves_like 'single capture' end context 'on []' do let(:pattern) { '(send (int $[!odd? !zero?]) :inc)' } let(:ruby) { '2.inc' } let(:captured_val) { 2 } it_behaves_like 'single capture' end context 'on a node type' do let(:pattern) { '(send $int :inc)' } let(:ruby) { '5.inc' } let(:captured_val) { s(:int, 5) } it_behaves_like 'single capture' end context 'on a literal' do let(:pattern) { '(send int $:inc)' } let(:ruby) { '5.inc' } let(:captured_val) { :inc } it_behaves_like 'single capture' end context 'when nested' do let(:pattern) { '(send $(int $_) :inc)' } let(:ruby) { '5.inc' } let(:captured_vals) { [s(:int, 5), 5] } it_behaves_like 'multiple capture' end end describe 'captures on ...' do context 'with no remaining pattern at the end' do let(:pattern) { '(send $...)' } let(:ruby) { '5.inc' } let(:captured_val) { [s(:int, 5), :inc] } it_behaves_like 'single capture' end context 'with a remaining node type at the end' do let(:pattern) { '(send $... int)' } let(:ruby) { '5 + 4' } let(:captured_val) { [s(:int, 5), :+] } it_behaves_like 'single capture' end context 'with remaining patterns at the end' do let(:pattern) { '(send $... int int)' } let(:ruby) { '[].push(1, 2, 3)' } let(:captured_val) { [s(:array), :push, s(:int, 1)] } it_behaves_like 'single capture' end context 'with a remaining sequence at the end' do let(:pattern) { '(send $... (int 4))' } let(:ruby) { '5 + 4' } let(:captured_val) { [s(:int, 5), :+] } it_behaves_like 'single capture' end context 'with a remaining set at the end' do let(:pattern) { '(send $... {int float})' } let(:ruby) { '5 + 4' } let(:captured_val) { [s(:int, 5), :+] } it_behaves_like 'single capture' end context 'with a remaining [] at the end' do let(:pattern) { '(send $... [(int even?) (int zero?)])' } let(:ruby) { '5 + 0' } let(:captured_val) { [s(:int, 5), :+] } it_behaves_like 'single capture' end context 'with a remaining literal at the end' do let(:pattern) { '(send $... :inc)' } let(:ruby) { '5.inc' } let(:captured_val) { [s(:int, 5)] } it_behaves_like 'single capture' end context 'with a remaining wildcard at the end' do let(:pattern) { '(send $... _)' } let(:ruby) { '5.inc' } let(:captured_val) { [s(:int, 5)] } it_behaves_like 'single capture' end context 'with a remaining capture at the end' do let(:pattern) { '(send $... $_)' } let(:ruby) { '5 + 4' } let(:captured_vals) { [[s(:int, 5), :+], s(:int, 4)] } it_behaves_like 'multiple capture' end context 'at the very beginning of a sequence' do let(:pattern) { '($... (int 1))' } let(:ruby) { '10 * 1' } let(:captured_val) { [s(:int, 10), :*] } it_behaves_like 'single capture' end context 'after a child' do let(:pattern) { '(send (int 10) $...)' } let(:ruby) { '10 * 1' } let(:captured_val) { [:*, s(:int, 1)] } it_behaves_like 'single capture' end end describe 'captures within union' do context 'on simple subpatterns' do let(:pattern) { '{$send $int $float}' } let(:ruby) { '2.0' } let(:captured_val) { s(:float, 2.0) } it_behaves_like 'single capture' end context 'within nested sequences' do let(:pattern) { '{(send $_ $_) (const $_ $_)}' } let(:ruby) { 'Namespace::CONST' } let(:captured_vals) { [s(:const, nil, :Namespace), :CONST] } it_behaves_like 'multiple capture' end context 'with complex nesting' do let(:pattern) do '{(send {$int $float} {$:inc $:dec}) ' \ '[!nil {($_ sym $_) (send ($_ $_) :object_id)}]}' end let(:ruby) { '10.object_id' } let(:captured_vals) { [:int, 10] } it_behaves_like 'multiple capture' end context 'with a different number of captures in each branch' do let(:pattern) { '{(send $...) (int $...) (send $_ $_)}' } it_behaves_like 'invalid' end end describe 'negation' do context 'on a symbol' do let(:pattern) { '(send _ !:abc)' } context 'with a matching symbol' do let(:ruby) { 'obj.abc' } it_behaves_like 'nonmatching' end context 'with a non-matching symbol' do let(:ruby) { 'obj.xyz' } it { expect(pattern).to match_code(node) } end context 'with a non-matching symbol, but too many children' do let(:ruby) { 'obj.xyz(1)' } it_behaves_like 'nonmatching' end end context 'on a string' do let(:pattern) { '(send (str !"foo") :upcase)' } context 'with a matching string' do let(:ruby) { '"foo".upcase' } it_behaves_like 'nonmatching' end context 'with a non-matching symbol' do let(:ruby) { '"bar".upcase' } it { expect(pattern).to match_code(node) } end end context 'on a set' do let(:pattern) { '(ivasgn _ !(int {1 2}))' } context 'with a matching value' do let(:ruby) { '@a = 1' } it_behaves_like 'nonmatching' end context 'with a non-matching value' do let(:ruby) { '@a = 3' } it { expect(pattern).to match_code(node) } end end context 'on a sequence' do let(:pattern) { '!(ivasgn :@a ...)' } context 'with a matching node' do let(:ruby) { '@a = 1' } it_behaves_like 'nonmatching' end context 'with a node of different type' do let(:ruby) { '@@a = 1' } it { expect(pattern).to match_code(node) } end context 'with a node with non-matching children' do let(:ruby) { '@b = 1' } it { expect(pattern).to match_code(node) } end end context 'on square brackets' do let(:pattern) { '![!int !float]' } context 'with a node which meets all requirements of []' do let(:ruby) { '"abc"' } it_behaves_like 'nonmatching' end context 'with a node which meets only 1 requirement of []' do let(:ruby) { '1' } it { expect(pattern).to match_code(node) } end end context 'when nested in complex ways' do let(:pattern) { '!(send !{int float} !:+ !(send _ :to_i))' } context 'with (send str :+ (send str :to_i))' do let(:ruby) { '"abc" + "1".to_i' } it { expect(pattern).to match_code(node) } end context 'with (send int :- int)' do let(:ruby) { '1 - 1' } it { expect(pattern).to match_code(node) } end context 'with (send str :<< str)' do let(:ruby) { '"abc" << "xyz"' } it_behaves_like 'nonmatching' end end end describe 'ellipsis' do context 'preceding a capture' do let(:pattern) { '(send array :push ... $_)' } let(:ruby) { '[1].push(2, 3, 4)' } let(:captured_val) { s(:int, 4) } it_behaves_like 'single capture' end context 'preceding multiple captures' do let(:pattern) { '(send array :push ... $_ $_)' } let(:ruby) { '[1].push(2, 3, 4, 5)' } let(:captured_vals) { [s(:int, 4), s(:int, 5)] } it_behaves_like 'multiple capture' end context 'with a wildcard at the end, but no remaining child to match it' do let(:pattern) { '(send array :zip array ... _)' } let(:ruby) { '[1,2].zip([3,4])' } it_behaves_like 'nonmatching' end context 'with a nodetype at the end, but no remaining child to match it' do let(:pattern) { '(send array :zip array ... array)' } let(:ruby) { '[1,2].zip([3,4])' } it_behaves_like 'nonmatching' end context 'with a nested sequence at the end, but no remaining child' do let(:pattern) { '(send array :zip array ... (array ...))' } let(:ruby) { '[1,2].zip([3,4])' } it_behaves_like 'nonmatching' end context 'with a set at the end, but no remaining child to match it' do let(:pattern) { '(send array :zip array ... {array})' } let(:ruby) { '[1,2].zip([3,4])' } it_behaves_like 'nonmatching' end context 'with [] at the end, but no remaining child to match it' do let(:pattern) { '(send array :zip array ... [array !nil])' } let(:ruby) { '[1,2].zip([3,4])' } it_behaves_like 'nonmatching' end context 'at the very beginning of a sequence' do let(:pattern) { '(... (int 1))' } let(:ruby) { '10 * 1' } it { expect(pattern).to match_code(node) } end end describe 'predicates' do context 'in root position' do let(:pattern) { 'send_type?' } let(:ruby) { '1.inc' } it { expect(pattern).to match_code(node) } context 'with name containing a numeral' do before { RuboCop::AST::Node.def_node_matcher :custom_42?, 'send_type?' } let(:pattern) { 'custom_42?' } it { expect(pattern).to match_code(node) } end end context 'at head position of a sequence' do # called on the type symbol let(:pattern) { '(!nil? int ...)' } let(:ruby) { '1.inc' } it { expect(pattern).to match_code(node) } end context 'applied to an integer for which the predicate is true' do let(:pattern) { '(send (int odd?) :inc)' } let(:ruby) { '1.inc' } it { expect(pattern).to match_code(node) } end context 'applied to an integer for which the predicate is false' do let(:pattern) { '(send (int odd?) :inc)' } let(:ruby) { '2.inc' } it_behaves_like 'nonmatching' end context 'when captured' do let(:pattern) { '(send (int $odd?) :inc)' } let(:ruby) { '1.inc' } let(:captured_val) { 1 } it_behaves_like 'single capture' end context 'when negated' do let(:pattern) { '(send int !nil?)' } let(:ruby) { '1.inc' } it { expect(pattern).to match_code(node) } end context 'when in last-child position, but all children have already ' \ 'been matched' do let(:pattern) { '(send int :inc ... !nil?)' } let(:ruby) { '1.inc' } it_behaves_like 'nonmatching' end context 'with one extra argument' do let(:pattern) { '(send (int equal?(%1)) ...)' } let(:ruby) { '1 + 2' } context 'for which the predicate is true' do let(:params) { [1] } it { expect(pattern).to match_code(node, 1) } end context 'for which the predicate is false' do let(:params) { [2] } it_behaves_like 'nonmatching' end end context 'with a named argument' do let(:pattern) { '(send (int equal?(%param)) ...)' } let(:ruby) { '1 + 2' } context 'for which the predicate is true' do let(:keyword_params) { { param: 1 } } it { expect(pattern).to match_code(node, param: 1) } end context 'for which the predicate is false' do let(:keyword_params) { { param: 2 } } it_behaves_like 'nonmatching' end context 'when not given' do let(:keyword_params) { {} } it 'raises an error' do expect { result }.to raise_error(ArgumentError) end end context 'with extra arguments' do let(:keyword_params) { { param: 1, extra: 2 } } it 'raises an error' do expect { result }.to raise_error(ArgumentError) end end end context 'with a constant argument' do let(:pattern) { '(send (int equal?(%CONST)) ...)' } let(:ruby) { '1 + 2' } before { stub_const 'CONST', const_value } context 'for which the predicate is true' do let(:const_value) { 1 } it { expect(pattern).to match_code(node) } end context 'for which the predicate is false' do let(:const_value) { 2 } it_behaves_like 'nonmatching' end end context 'with an expression argument' do before do def instance.some_function(node, arg) arg === node # rubocop:disable Style/CaseEquality end end let(:pattern) { '(send (int _value) :+ #some_function( {(int _value) (float _value)} ) )' } context 'for which the predicate is true' do let(:ruby) { '2 + 2.0' } it { expect(instance).to match_code(node) } end context 'for which the predicate is false' do let(:ruby) { '2 + 3.0' } it_behaves_like 'nonmatching' end end context 'with multiple arguments' do let(:pattern) { '(str between?(%1, %2))' } let(:ruby) { '"c"' } context 'for which the predicate is true' do let(:params) { %w[a d] } it { expect(pattern).to match_code(node, 'a', 'd') } end context 'for which the predicate is false' do let(:params) { %w[a b] } it_behaves_like 'nonmatching' end end end describe 'params' do context 'in root position' do let(:pattern) { '%1' } let(:params) { [s(:int, 10)] } let(:ruby) { '10' } it { expect(pattern).to match_code(node, s(:int, 10)) } context 'in root position' do let(:pattern) { '%1' } let(:matcher) { Object.new } let(:params) { [matcher] } let(:ruby) { '10' } before { expect(matcher).to receive(:===).with(s(:int, 10)).and_return true } # rubocop:todo RSpec/ExpectInHook it { expect(pattern).to match_code(node, matcher) } end end context 'as named parameters' do let(:pattern) { '%foo' } let(:matcher) { Object.new } let(:keyword_params) { { foo: matcher } } let(:ruby) { '10' } context 'when provided as argument to match' do before { expect(matcher).to receive(:===).with(s(:int, 10)).and_return true } # rubocop:todo RSpec/ExpectInHook it { expect(pattern).to match_code(node, foo: matcher) } end context 'when extra are provided' do let(:keyword_params) { { foo: matcher, bar: matcher } } it 'raises an ArgumentError' do expect { result }.to raise_error(ArgumentError) end end context 'when not provided' do let(:keyword_params) { {} } it 'raises an ArgumentError' do expect { result }.to raise_error(ArgumentError) end end end context 'in a nested sequence' do let(:pattern) { '(send (send _ %2) %1)' } let(:params) { %i[inc dec] } let(:ruby) { '5.dec.inc' } it { expect(pattern).to match_code(node, :inc, :dec) } end context 'when preceded by ...' do let(:pattern) { '(send ... %1)' } let(:params) { [s(:int, 10)] } let(:ruby) { '1 + 10' } it { expect(pattern).to match_code(node, s(:int, 10)) } end context 'when preceded by $...' do let(:pattern) { '(send $... %1)' } let(:params) { [s(:int, 10)] } let(:ruby) { '1 + 10' } let(:captured_val) { [s(:int, 1), :+] } it_behaves_like 'single capture' end context 'when captured' do let(:pattern) { '(const _ $%1)' } let(:params) { [:A] } let(:ruby) { 'Namespace::A' } let(:captured_val) { :A } it_behaves_like 'single capture' end context 'when negated, with a matching value' do let(:pattern) { '(const _ !%1)' } let(:params) { [:A] } let(:ruby) { 'Namespace::A' } it_behaves_like 'nonmatching' end context 'when negated, with a nonmatching value' do let(:pattern) { '(const _ !%1)' } let(:params) { [:A] } let(:ruby) { 'Namespace::B' } it { expect(pattern).to match_code(node, :A) } end context 'without explicit number' do let(:pattern) { '(const %2 %)' } let(:params) { [:A, s(:const, nil, :Namespace)] } let(:ruby) { 'Namespace::A' } it { expect(pattern).to match_code(node, :A, s(:const, nil, :Namespace)) } end context 'when inside a union, with a matching value' do let(:pattern) { '{str (int %)}' } let(:params) { [10] } let(:ruby) { '10' } it { expect(pattern).to match_code(node, 10) } end context 'when inside a union, with a nonmatching value' do let(:pattern) { '{str (int %)}' } let(:params) { [10] } let(:ruby) { '1.0' } it_behaves_like 'nonmatching' end context 'when inside an intersection' do let(:pattern) { '(int [!%1 %2 !zero?])' } let(:params) { [10, 20] } let(:ruby) { '20' } it { expect(pattern).to match_code(node, 10, 20) } end context 'param number zero' do # refers to original target node passed to #match let(:pattern) { '^(send %0 :+ (int 2))' } let(:ruby) { '1 + 2' } context 'in a position which matches original target node' do let(:node) { root_node.children[0] } it { expect(pattern).to match_code(node) } end context 'in a position which does not match original target node' do let(:node) { root_node.children[2] } it_behaves_like 'nonmatching' end end end describe 'caret (ascend)' do context 'used with a node type' do let(:ruby) { '1.inc' } let(:node) { root_node.children[0] } context 'which matches' do let(:pattern) { '^send' } it { expect(pattern).to match_code(node) } end context "which doesn't match" do let(:pattern) { '^const' } it_behaves_like 'nonmatching' end end context 'within sequence' do let(:ruby) { '1.inc' } context 'not in head' do let(:ruby) { '1.inc' } let(:pattern) { '(send ^send :inc)' } it { expect(pattern).to match_code(node) } context 'of a sequence' do let(:pattern) { '(send ^(send _ _) :inc)' } it { expect(pattern).to match_code(node) } end end context 'in head' do let(:node) { root_node.children[0] } let(:pattern) { '(^send 1)' } it { expect(pattern).to match_code(node) } context 'of a sequence' do let(:pattern) { '(^(send _ _) 1)' } it { expect(pattern).to match_code(node) } end end end context 'repeated twice' do # ascends to grandparent node let(:pattern) { '^^block' } let(:ruby) { '1.inc { something }' } let(:node) { root_node.children[0].children[0] } it { expect(pattern).to match_code(node) } end context 'inside an intersection' do let(:pattern) { '^[!nil send ^(block ...)]' } let(:ruby) { '1.inc { something }' } let(:node) { root_node.children[0].children[0] } it { expect(pattern).to match_code(node) } end context 'inside a union' do let(:pattern) { '{^send ^^send}' } let(:ruby) { '"str".concat(local += "abc")' } let(:node) { root_node.children[2].children[2] } it { expect(pattern).to match_code(node) } end # NOTE!! a pitfall of doing this is that unification is done using #== # This means that 'identical' AST nodes, which are not really identical # because they have different metadata, will still unify context 'using unification to match self within parent' do let(:pattern) { '[_self ^(send _ _ _self)]' } let(:ruby) { '1 + 2' } context 'with self in the right position' do let(:node) { root_node.children[2] } it { expect(pattern).to match_code(node) } end context 'with self in the wrong position' do let(:node) { root_node.children[0] } it_behaves_like 'nonmatching' end end end describe 'funcalls' do module RuboCop # rubocop:disable Lint/ConstantDefinitionInBlock module AST # Add test function calls class NodePattern def goodmatch(_foo) true end def badmatch(_foo) false end def witharg(foo, bar) foo == bar end def withargs(foo, bar, qux) foo.between?(bar, qux) end end end end context 'without extra arguments' do let(:pattern) { '(lvasgn #goodmatch ...)' } let(:ruby) { 'a = 1' } it { expect(pattern).to match_code(node) } end context 'with one argument' do let(:pattern) { '(str #witharg(%1))' } let(:ruby) { '"foo"' } let(:params) { %w[foo] } it { expect(pattern).to match_code(node, 'foo') } end context 'with multiple arguments' do let(:pattern) { '(str #withargs(%1, %2))' } let(:ruby) { '"c"' } let(:params) { %w[a d] } it { expect(pattern).to match_code(node, 'a', 'd') } end end describe 'commas' do context 'with commas randomly strewn around' do let(:pattern) { ',,(,send,, ,int,:+, int ), ' } it_behaves_like 'invalid' end end describe 'in any order' do let(:ruby) { '[:hello, "world", 1, 2, 3]' } context 'without ellipsis' do context 'with matching children' do let(:pattern) { '(array <(str $_) (int 1) (int 3) (int $_) $_>)' } let(:captured_vals) { ['world', 2, s(:sym, :hello)] } it_behaves_like 'multiple capture' end context 'with too many children' do let(:pattern) { '(array <(str $_) (int 1) (int 3) (int $_)>)' } it_behaves_like 'nonmatching' end context 'with too few children' do let(:pattern) { '(array <(str $_) (int 1) (int 3) (int $_) _ _>)' } it_behaves_like 'nonmatching' end end context 'with a captured ellipsis' do context 'matching non sequential children' do let(:pattern) { '(array <(str "world") (int 2) $...>)' } let(:captured_val) { [s(:sym, :hello), s(:int, 1), s(:int, 3)] } it_behaves_like 'single capture' end context 'matching all children' do let(:pattern) { '(array <(str "world") (int 2) _ _ _ $...>)' } let(:captured_val) { [] } it_behaves_like 'single capture' end context 'nested' do let(:ruby) { '[:x, 1, [:y, 2, 3], 42]' } let(:pattern) { '(array <(int $_) (array <(int $_) $...>) $...>)' } let(:captured_vals) do [1, 2, [s(:sym, :y), s(:int, 3)], [s(:sym, :x), s(:int, 42)]] end it_behaves_like 'multiple capture' end end context 'with an ellipsis' do let(:pattern) { '(array <(str "world") (int 2) ...> $_)' } let(:captured_val) { s(:int, 3) } it_behaves_like 'single capture' end context 'captured' do context 'without ellipsis' do let(:pattern) { '(array sym $)' } let(:captured_val) { node.children.last(4) } it_behaves_like 'single capture' end end context 'doubled' do context 'separated by fixed argument' do let(:pattern) { '(array <(str $_) (sym $_)> $_ <(int 3) (int $_)>)' } let(:captured_vals) { ['world', :hello, s(:int, 1), 2] } it_behaves_like 'multiple capture' end context 'separated by an ellipsis' do let(:pattern) { '(array <(str $_) (sym $_)> $... <(int 3) (int $_)>)' } let(:captured_vals) { ['world', :hello, [s(:int, 1)], 2] } it_behaves_like 'multiple capture' end end describe 'invalid' do context 'at the beginning of a sequence' do let(:pattern) { '(<(str $_) (sym $_)> ...)' } it_behaves_like 'invalid' end context 'containing ellipsis not at the end' do let(:pattern) { '(array <(str $_) ... (sym $_)>)' } it_behaves_like 'invalid' end context 'with an ellipsis inside and outside' do let(:pattern) { '(array <(str $_) (sym $_) ...> ...)' } it_behaves_like 'invalid' end context 'doubled with ellipsis' do let(:pattern) { '(array <(str $_) ...> <(str $_) ...>)' } it_behaves_like 'invalid' end context 'nested' do let(:pattern) { '(array <(str $_) > ...)' } it_behaves_like 'invalid' end end end describe 'repeated' do let(:ruby) { '[:hello, 1, 2, 3]' } shared_examples 'repeated pattern' do context 'with one match' do let(:pattern) { "(array sym int $int #{symbol} int)" } let(:captured_val) { [s(:int, 2)] } it_behaves_like 'single capture' end context 'at beginning of sequence' do let(:pattern) { "(int #{symbol} int)" } it_behaves_like 'invalid' end context 'with an ellipsis in the same sequence' do let(:pattern) { "(array int #{symbol} ...)" } it_behaves_like 'invalid' end end context 'using *' do let(:symbol) { :* } it_behaves_like 'repeated pattern' context 'without capture' do let(:pattern) { '(array sym int* int)' } it { expect(pattern).to match_code(node) } end context 'with matching children' do let(:pattern) { '(array sym $int* int)' } let(:captured_val) { [s(:int, 1), s(:int, 2)] } it_behaves_like 'single capture' end context 'with zero match' do let(:pattern) { '(array sym int int $sym* int)' } let(:captured_val) { [] } it_behaves_like 'single capture' end context 'with no match' do let(:pattern) { '(array sym int $sym* int)' } it_behaves_like 'nonmatching' end context 'with multiple subcaptures' do let(:pattern) { '(array ($_ $_)* int int)' } let(:captured_vals) { [%i[sym int], [:hello, 1]] } it_behaves_like 'multiple capture' end context 'nested with multiple subcaptures' do let(:ruby) { '[[:hello, 1, 2, 3], [:world, 3, 4]]' } let(:pattern) { '(array (array (sym $_) (int $_)*)*)' } let(:captured_vals) { [%i[hello world], [[1, 2, 3], [3, 4]]] } it_behaves_like 'multiple capture' end end context 'using +' do let(:symbol) { :+ } it_behaves_like 'repeated pattern' context 'with matching children' do let(:pattern) { '(array sym $int+ int)' } let(:captured_val) { [s(:int, 1), s(:int, 2)] } it_behaves_like 'single capture' end context 'with zero match' do let(:pattern) { '(array sym int int $sym+ int)' } it_behaves_like 'nonmatching' end end context 'using ?' do let(:symbol) { '?' } it_behaves_like 'repeated pattern' context 'with too many matching children' do let(:pattern) { '(array sym $int ? int)' } let(:captured_val) { [s(:int, 1), s(:int, 2)] } it_behaves_like 'nonmatching' end context 'with zero match' do let(:pattern) { '(array sym int int $(sym _)? int)' } let(:captured_val) { [] } it_behaves_like 'single capture' end end end describe 'descend' do let(:ruby) { '[1, [[2, 3, [[5]]], 4]]' } context 'with an immediate match' do let(:pattern) { '(array `$int _)' } let(:captured_val) { s(:int, 1) } it_behaves_like 'single capture' end context 'with a match multiple levels, depth first' do let(:pattern) { '(array (int 1) `$int)' } let(:captured_val) { s(:int, 2) } it_behaves_like 'single capture' end context 'nested' do let(:pattern) { '(array (int 1) `(array <`(array $int) ...>))' } let(:captured_val) { s(:int, 5) } it_behaves_like 'single capture' end context 'with a literal match' do let(:pattern) { '(array (int 1) `4)' } it { expect(pattern).to match_code(node) } end context 'without match' do let(:pattern) { '(array `$str ...)' } it_behaves_like 'nonmatching' end end describe 'bad syntax' do context 'with empty parentheses' do let(:pattern) { '()' } it_behaves_like 'invalid' end context 'with empty union' do let(:pattern) { '{}' } it_behaves_like 'invalid' end context 'with empty intersection' do let(:pattern) { '[]' } it_behaves_like 'invalid' end context 'with unmatched opening paren' do let(:pattern) { '(send (const)' } it_behaves_like 'invalid' end context 'with unmatched opening paren and `...`' do let(:pattern) { '(send ...' } it_behaves_like 'invalid' end context 'with unmatched closing paren' do let(:pattern) { '(send (const)))' } it_behaves_like 'invalid' end context 'with unmatched opening curly' do let(:pattern) { '{send const' } it_behaves_like 'invalid' end context 'with unmatched closing curly' do let(:pattern) { '{send const}}' } it_behaves_like 'invalid' end context 'with negated closing paren' do let(:pattern) { '(send (const) !)' } it_behaves_like 'invalid' end context 'with negated closing curly' do let(:pattern) { '{send const !}' } it_behaves_like 'invalid' end context 'with negated ellipsis' do let(:pattern) { '(send !...)' } it_behaves_like 'invalid' end context 'with doubled ellipsis' do let(:pattern) { '(send ... ...)' } it_behaves_like 'invalid' end context 'with doubled comma in arg list' do let(:pattern) { '(send #func(:foo, ,:bar))' } it_behaves_like 'invalid' end context 'with leading comma in arg list' do let(:pattern) { '(send #func(, :foo))' } it_behaves_like 'invalid' end end describe 'comments' do let(:pattern) { "(int # We want an int\n$_) # Let's capture the value" } let(:ruby) { '42' } let(:captured_val) { 42 } it_behaves_like 'single capture' end describe '.descend' do let(:ruby) { '[[1, 2], 3]' } it 'yields all children depth first' do e = described_class.descend(node) expect(e.instance_of?(Enumerator)).to be(true) array, three = node.children one, two = array.children expect(e.to_a).to eq([node, array, one, 1, two, 2, three, 3]) end it 'yields the given argument if it is not a Node' do expect(described_class.descend(42).to_a).to eq([42]) end end context 'macros' do include RuboCop::AST::Sexp subject(:result) do if keyword_params.empty? # Avoid bug in Ruby < 2.7 defined_class.new.send(method_name, node, *params) else defined_class.new.send(method_name, node, *params, **keyword_params) end end before do stub_const('MyClass', Class.new do extend RuboCop::AST::NodePattern::Macros end) end let(:keyword_defaults) { {} } let(:method_name) { :my_matcher } let(:line_no) { __LINE__ + 2 } let(:defined_class) do MyClass.public_send helper_name, method_name, pattern, **keyword_defaults MyClass end let(:ruby) { ':hello' } let(:instance) { defined_class.new } if Set[1] === 1 # rubocop:disable Style/CaseEquality let(:hello_matcher) { Set[:hello, :foo] } else let(:hello_matcher) { Set[:hello, :foo].method(:include?).to_proc } end context 'with a pattern without captures' do let(:pattern) { '(sym _)' } context 'def_node_matcher' do let(:helper_name) { :def_node_matcher } context 'when called on matching code' do it { expect(instance).to match_code(node) } end context 'when called on non-matching code' do let(:ruby) { '"world"' } it_behaves_like 'nonmatching' end context 'when it errors' do let(:params) { [:extra] } it 'raises an error with the right location' do expect { result }.to(raise_error do |err| expect(err.is_a?(ArgumentError)).to be(true) expect(err.message).to include('wrong number of arguments') expect(err.backtrace_locations.first.lineno).to be(line_no) end) end end end context 'def_node_search' do let(:helper_name) { :def_node_search } let(:ruby) { 'foo(:hello, :world)' } context('without a predicate name') do context 'when called on matching code' do it 'returns an enumerator yielding the matches' do expect(result.is_a?(Enumerator)).to be(true) expect(result.to_a).to match_array [s(:sym, :hello), s(:sym, :world)] end end context 'when called on non-matching code' do let(:ruby) { 'foo("hello", "world")' } it 'returns an enumerator yielding nothing' do expect(result.is_a?(Enumerator)).to be(true) expect(result.to_a).to eq [] end end context 'when it errors' do let(:params) { [:extra] } it 'raises an error with the right location' do expect { result }.to(raise_error do |err| expect(err.is_a?(ArgumentError)).to be(true) expect(err.message).to include('wrong number of arguments') expect(err.backtrace_locations.first.lineno).to be(line_no) end) end end end context('with a predicate name') do let(:method_name) { :my_matcher? } context 'when called on matching code' do it { expect(instance).to match_code(node) } end context 'when called on non-matching code' do let(:ruby) { '"world"' } it_behaves_like 'nonmatching' end context 'when it errors' do let(:params) { [:extra] } it 'raises an error with the right location' do expect { result }.to(raise_error do |err| expect(err.is_a?(ArgumentError)).to be(true) expect(err.message).to include('wrong number of arguments') expect(err.backtrace_locations.first.lineno).to be(line_no) end) end end end end end context 'with a pattern with captures' do let(:pattern) { '(sym $_)' } context 'def_node_matcher' do let(:helper_name) { :def_node_matcher } context 'when called on matching code' do it { is_expected.to eq :hello } end context 'when called on non-matching code' do let(:ruby) { '"world"' } it_behaves_like 'nonmatching' end context 'when it errors' do let(:params) { [:extra] } it 'raises an error with the right location' do expect { result }.to(raise_error do |err| expect(err.is_a?(ArgumentError)).to be(true) expect(err.message).to include('wrong number of arguments') expect(err.backtrace_locations.first.lineno).to be(line_no) end) end end end context 'def_node_search' do let(:helper_name) { :def_node_search } let(:ruby) { 'foo(:hello, :world)' } context('without a predicate name') do context 'when called on matching code' do it 'returns an enumerator yielding the captures' do expect(result.is_a?(Enumerator)).to be(true) expect(result.to_a).to match_array %i[hello world] end context 'when the pattern contains keyword_params' do let(:pattern) { '(sym $%foo)' } let(:keyword_params) { { foo: hello_matcher } } it 'returns an enumerator yielding the captures' do expect(result.is_a?(Enumerator)).to be(true) expect(result.to_a).to match_array %i[hello] end # rubocop:disable RSpec/NestedGroups context 'when helper is called with default keyword_params' do let(:keyword_defaults) { { foo: :world } } it 'is overriden when calling the matcher' do expect(result.is_a?(Enumerator)).to be(true) expect(result.to_a).to match_array %i[hello] end context 'and no value is given to the matcher' do let(:keyword_params) { {} } it 'uses the defaults' do expect(result.is_a?(Enumerator)).to be(true) expect(result.to_a).to match_array %i[world] end end context 'some defaults are not params' do let(:keyword_defaults) { { bar: :world } } it 'raises an error' do expect { result }.to raise_error(ArgumentError) end end end # rubocop:enable RSpec/NestedGroups end end context 'when called on non-matching code' do let(:ruby) { 'foo("hello", "world")' } it 'returns an enumerator yielding nothing' do expect(result.is_a?(Enumerator)).to be(true) expect(result.to_a).to eq [] end end context 'when it errors' do let(:params) { [:extra] } it 'raises an error with the right location' do expect { result }.to(raise_error do |err| expect(err.is_a?(ArgumentError)).to be(true) expect(err.message).to include('wrong number of arguments') expect(err.backtrace_locations.first.lineno).to be(line_no) end) end end end context('with a predicate name') do let(:method_name) { :my_matcher? } context 'when called on matching code' do it { expect(instance).to match_code(node) } end context 'when called on non-matching code' do let(:ruby) { '"world"' } it_behaves_like 'nonmatching' end context 'when it errors' do let(:params) { [:extra] } it 'raises an error with the right location' do expect { result }.to(raise_error do |err| expect(err.is_a?(ArgumentError)).to be(true) expect(err.message).to include('wrong number of arguments') expect(err.backtrace_locations.first.lineno).to be(line_no) end) end end end end end context 'with a pattern with a constant' do let(:pattern) { '(sym %TEST)' } let(:helper_name) { :def_node_matcher } before { defined_class::TEST = hello_matcher } it { expect(instance).to match_code(node) } context 'when the value is not in the set' do let(:ruby) { ':world' } it_behaves_like 'nonmatching' end end end end rubocop-ast-0.6.0/spec/rubocop/ast/node_spec.rb000066400000000000000000000375431373365104000214470ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::Node do let(:ast) { RuboCop::AST::ProcessedSource.new(src, ruby_version).ast } let(:node) { ast } describe '#value_used?' do before :all do module RuboCop # rubocop:disable Lint/ConstantDefinitionInBlock module AST # Patch Node class Node # Let's make our predicate matchers read better def used? value_used? end end end end end context 'at the top level' do let(:src) { 'expr' } it 'is false' do expect(node.used?).to be(false) end end context 'within a method call node' do let(:src) { 'obj.method(arg1, arg2, arg3)' } it 'is always true' do expect(node.child_nodes).to all(be_used) end end context 'at the end of a block' do let(:src) { 'obj.method { blah; expr }' } it 'is always true' do expect(node.children.last.used?).to be(true) end end context 'within a class definition node' do let(:src) { 'class C < Super; def a; 1; end; self; end' } it 'is always true' do expect(node.child_nodes).to all(be_used) end end context 'within a module definition node' do let(:src) { 'module M; def method; end; 1; end' } it 'is always true' do expect(node.child_nodes).to all(be_used) end end context 'within a singleton class node' do let(:src) { 'class << obj; 1; 2; end' } it 'is always true' do expect(node.child_nodes).to all(be_used) end end context 'within an if...else..end node' do context 'nested in a method call' do let(:src) { 'obj.method(if a then b else c end)' } it 'is always true' do if_node = node.children[2] expect(if_node.child_nodes).to all(be_used) end end context 'at the top level' do let(:src) { 'if a then b else c end' } it 'is true only for the condition' do expect(node.condition.used?).to be(true) expect(node.if_branch.used?).to be(false) expect(node.else_branch.used?).to be(false) end end end context 'within an array literal' do context 'assigned to an ivar' do let(:src) { '@var = [a, b, c]' } it 'is always true' do ary_node = node.children[1] expect(ary_node.child_nodes).to all(be_used) end end context 'at the top level' do let(:src) { '[a, b, c]' } it 'is always false' do expect(node.child_nodes.map(&:used?)).to all(be false) end end end context 'within a while node' do let(:src) { 'while a; b; end' } it 'is true only for the condition' do expect(node.condition.used?).to be(true) expect(node.body.used?).to be(false) end end end describe '#recursive_basic_literal?' do shared_examples 'literal' do |source| let(:src) { source } it "returns true for `#{source}`" do expect(node.recursive_literal?).to be(true) end end it_behaves_like 'literal', '!true' it_behaves_like 'literal', '"#{2}"' it_behaves_like 'literal', '(1)' it_behaves_like 'literal', '(false && true)' it_behaves_like 'literal', '(false <=> true)' it_behaves_like 'literal', '(false or true)' it_behaves_like 'literal', '[1, 2, 3]' it_behaves_like 'literal', '{ :a => 1, :b => 2 }' it_behaves_like 'literal', '{ a: 1, b: 2 }' it_behaves_like 'literal', '/./' it_behaves_like 'literal', '%r{abx}ixo' it_behaves_like 'literal', '1.0' it_behaves_like 'literal', '1' it_behaves_like 'literal', 'false' it_behaves_like 'literal', 'nil' it_behaves_like 'literal', "'str'" shared_examples 'non literal' do |source| let(:src) { source } it "returns false for `#{source}`" do expect(node.recursive_literal?).to be(false) end end it_behaves_like 'non literal', '(x && false)' it_behaves_like 'non literal', '(x == false)' it_behaves_like 'non literal', '(x or false)' it_behaves_like 'non literal', '[some_method_call]' it_behaves_like 'non literal', '{ :sym => some_method_call }' it_behaves_like 'non literal', '{ some_method_call => :sym }' it_behaves_like 'non literal', '/.#{some_method_call}/' it_behaves_like 'non literal', '%r{abx#{foo}}ixo' it_behaves_like 'non literal', 'some_method_call' it_behaves_like 'non literal', 'some_method_call(x, y)' end describe '#pure?' do context 'for a method call' do let(:src) { 'obj.method(arg1, arg2)' } it 'returns false' do expect(node.pure?).to be(false) end end context 'for an integer literal' do let(:src) { '100' } it 'returns true' do expect(node.pure?).to be(true) end end context 'for an array literal' do context 'with only literal children' do let(:src) { '[1..100, false, :symbol, "string", 1.0]' } it 'returns true' do expect(node.pure?).to be(true) end end context 'which contains a method call' do let(:src) { '[1, 2, 3, 3 + 4]' } it 'returns false' do expect(node.pure?).to be(false) end end end context 'for a hash literal' do context 'with only literal children' do let(:src) { '{range: 1..100, bool: false, str: "string", float: 1.0}' } it 'returns true' do expect(node.pure?).to be(true) end end context 'which contains a method call' do let(:src) { '{a: 1, b: 2, c: Kernel.exit}' } it 'returns false' do expect(node.pure?).to be(false) end end end context 'for a nested if' do context 'where the innermost descendants are local vars and literals' do let(:src) do ['lvar1, lvar2 = method1, method2', 'if $global', ' if @initialized', ' [lvar1, lvar2, true]', ' else', ' :symbol', ' end', 'else', ' lvar1', 'end'].join("\n") end it 'returns true' do if_node = node.children[1] expect(if_node.type).to be :if expect(if_node.pure?).to be(true) end end context 'where one branch contains a method call' do let(:src) { 'if $DEBUG then puts "hello" else nil end' } it 'returns false' do expect(node.pure?).to be(false) end end context 'where one branch contains an assignment statement' do let(:src) { 'if @a then 1 else $global = "str" end' } it 'returns false' do expect(node.pure?).to be(false) end end end context 'for an ivar assignment' do let(:src) { '@var = 1' } it 'returns false' do expect(node.pure?).to be(false) end end context 'for a gvar assignment' do let(:src) { '$var = 1' } it 'returns false' do expect(node.pure?).to be(false) end end context 'for a cvar assignment' do let(:src) { '@@var = 1' } it 'returns false' do expect(node.pure?).to be(false) end end context 'for an lvar assignment' do let(:src) { 'var = 1' } it 'returns false' do expect(node.pure?).to be(false) end end context 'for a class definition' do let(:src) { 'class C < Super; def method; end end' } it 'returns false' do expect(node.pure?).to be(false) end end context 'for a module definition' do let(:src) { 'module M; def method; end end' } it 'returns false' do expect(node.pure?).to be(false) end end context 'for a regexp' do let(:opts) { '' } let(:body) { '' } let(:src) { "/#{body}/#{opts}" } context 'with interpolated segments' do let(:body) { '#{x}' } it 'returns false' do expect(node.pure?).to be(false) end end context 'with no interpolation' do let(:src) { URI::DEFAULT_PARSER.make_regexp.inspect } it 'returns true' do expect(node.pure?).to be(true) end end context 'with options' do let(:opts) { 'oix' } it 'returns true' do expect(node.pure?).to be(true) end end end end describe 'sibling_access' do let(:src) { '[0, 1, 2, 3, 4, 5]' } it 'returns trivial values for a root node' do expect(node.sibling_index).to eq nil expect(node.left_sibling).to eq nil expect(node.right_sibling).to eq nil expect(node.left_siblings).to eq [] expect(node.right_siblings).to eq [] end context 'for a node with siblings' do let(:node) { ast.children[2] } it 'returns the expected values' do expect(node.sibling_index).to eq 2 expect(node.left_sibling.value).to eq 1 expect(node.right_sibling.value).to eq 3 expect(node.left_siblings.map(&:value)).to eq [0, 1] expect(node.right_siblings.map(&:value)).to eq [3, 4, 5] end end context 'for a single child' do let(:src) { '[0]' } let(:node) { ast.children[0] } it 'returns the expected values' do expect(node.sibling_index).to eq 0 expect(node.left_sibling).to eq nil expect(node.right_sibling).to eq nil expect(node.left_siblings.map(&:value)).to eq [] expect(node.right_siblings.map(&:value)).to eq [] end end end describe '#argument_type?' do context 'block arguments' do let(:src) { 'bar { |a, b = 42, *c, d: 42, **e| nil }' } it 'returns true for all argument types' do node.arguments.children.each do |arg| expect(arg.argument_type?).to eq(true) end expect(node.arguments.argument_type?).to eq(false) end end context 'method arguments' do let(:src) { 'def method_name(a = 0, *b, c: 42, **d); end' } it 'returns true for all argument types' do node.arguments.children.each do |arg| expect(arg.argument_type?).to eq(true) end expect(node.arguments.argument_type?).to eq(false) end end end describe '#class_constructor?' do context 'class definition with a block' do let(:src) { 'Class.new { a = 42 }' } it 'matches' do expect(node.class_constructor?).to eq(true) end end context 'module definition with a block' do let(:src) { 'Module.new { a = 42 }' } it 'matches' do expect(node.class_constructor?).to eq(true) end end context 'class definition' do let(:src) { 'class Foo; a = 42; end' } it 'does not match' do expect(node.class_constructor?).to eq(nil) end end context 'class definition on outer scope' do let(:src) { '::Class.new { a = 42 }' } it 'matches' do expect(node.class_constructor?).to eq(true) end end end describe '#struct_constructor?' do context 'struct definition with a block' do let(:src) { 'Struct.new { a = 42 }' } it 'matches' do expect(node.struct_constructor?).to eq(node.body) end end context 'struct definition without block' do let(:src) { 'Struct.new(:foo, :bar)' } it 'does not match' do expect(node.struct_constructor?).to eq(nil) end end context '::Struct' do let(:src) { '::Struct.new { a = 42 }' } it 'matches' do expect(node.struct_constructor?).to eq(node.body) end end end describe '#class_definition?' do context 'without inheritance' do let(:src) { 'class Foo; a = 42; end' } it 'matches' do expect(node.class_definition?).to eq(node.body) end end context 'with inheritance' do let(:src) { 'class Foo < Bar; a = 42; end' } it 'matches' do expect(node.class_definition?).to eq(node.body) end end context 'with ::ClassName' do let(:src) { 'class ::Foo < Bar; a = 42; end' } it 'matches' do expect(node.class_definition?).to eq(node.body) end end context 'with Struct' do let(:src) do <<~RUBY Person = Struct.new(:name, :age) do a = 2 def details; end end RUBY end it 'matches' do class_node = node.children.last expect(class_node.class_definition?).to eq(class_node.body) end end context 'constant defined as Struct without block' do let(:src) { 'Person = Struct.new(:name, :age)' } it 'does not match' do expect(node.class_definition?).to eq(nil) end end context 'with Class.new' do let(:src) do <<~RUBY Person = Class.new do a = 2 def details; end end RUBY end it 'matches' do class_node = node.children.last expect(class_node.class_definition?).to eq(class_node.body) end end context 'namespaced class' do let(:src) do <<~RUBY class Foo::Bar::Baz BAZ = 2 def variables; end end RUBY end it 'matches' do expect(node.class_definition?).to eq(node.body) end end context 'with self singleton class' do let(:src) do <<~RUBY class << self BAZ = 2 def variables; end end RUBY end it 'matches' do expect(node.class_definition?).to eq(node.body) end end context 'with object singleton class' do let(:src) do <<~RUBY class << foo BAZ = 2 def variables; end end RUBY end it 'matches' do expect(node.class_definition?).to eq(node.body) end end end describe '#module_definition?' do context 'using module keyword' do let(:src) { 'module Foo; A = 42; end' } it 'matches' do expect(node.module_definition?).to eq(node.body) end end context 'with ::ModuleName' do let(:src) { 'module ::Foo; A = 42; end' } it 'matches' do expect(node.module_definition?).to eq(node.body) end end context 'with Module.new' do let(:src) do <<~RUBY Person = Module.new do a = 2 def details; end end RUBY end it 'matches' do module_node = node.children.last expect(module_node.module_definition?).to eq(module_node.body) end end context 'prepend Module.new' do let(:src) do <<~RUBY prepend(Module.new do a = 2 def details; end end) RUBY end it 'matches' do module_node = node.children.last expect(module_node.module_definition?).to eq(module_node.body) end end context 'nested modules' do let(:src) do <<~RUBY module Foo module Bar BAZ = 2 def variables; end end end RUBY end it 'matches' do expect(node.module_definition?).to eq(node.body) end end context 'namespaced modules' do let(:src) do <<~RUBY module Foo::Bar::Baz BAZ = 2 def variables; end end RUBY end it 'matches' do expect(node.module_definition?).to eq(node.body) end end context 'included module definition' do let(:src) do <<~RUBY include(Module.new do BAZ = 2 def variables; end end) RUBY end it 'matches' do module_node = node.children.last expect(module_node.module_definition?).to eq(module_node.body) end end end end rubocop-ast-0.6.0/spec/rubocop/ast/or_node_spec.rb000066400000000000000000000056721373365104000221450ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::OrNode do let(:or_node) { parse_source(source).ast } describe '.new' do context 'with a logical or node' do let(:source) do ':foo || :bar' end it { expect(or_node.is_a?(described_class)).to be(true) } end context 'with a semantic or node' do let(:source) do ':foo or :bar' end it { expect(or_node.is_a?(described_class)).to be(true) } end end describe '#logical_operator?' do context 'with a logical or node' do let(:source) do ':foo || :bar' end it { expect(or_node.logical_operator?).to be(true) } end context 'with a semantic or node' do let(:source) do ':foo or :bar' end it { expect(or_node.logical_operator?).to be(false) } end end describe '#semantic_operator?' do context 'with a logical or node' do let(:source) do ':foo || :bar' end it { expect(or_node.semantic_operator?).to be(false) } end context 'with a semantic or node' do let(:source) do ':foo or :bar' end it { expect(or_node.semantic_operator?).to be(true) } end end describe '#operator' do context 'with a logical or node' do let(:source) do ':foo || :bar' end it { expect(or_node.operator).to eq('||') } end context 'with a semantic or node' do let(:source) do ':foo or :bar' end it { expect(or_node.operator).to eq('or') } end end describe '#alternate_operator' do context 'with a logical or node' do let(:source) do ':foo || :bar' end it { expect(or_node.alternate_operator).to eq('or') } end context 'with a semantic or node' do let(:source) do ':foo or :bar' end it { expect(or_node.alternate_operator).to eq('||') } end end describe '#inverse_operator' do context 'with a logical or node' do let(:source) do ':foo || :bar' end it { expect(or_node.inverse_operator).to eq('&&') } end context 'with a semantic or node' do let(:source) do ':foo or :bar' end it { expect(or_node.inverse_operator).to eq('and') } end end describe '#lhs' do context 'with a logical or node' do let(:source) do ':foo || 42' end it { expect(or_node.lhs.sym_type?).to be(true) } end context 'with a semantic or node' do let(:source) do ':foo or 42' end it { expect(or_node.lhs.sym_type?).to be(true) } end end describe '#rhs' do context 'with a logical or node' do let(:source) do ':foo || 42' end it { expect(or_node.rhs.int_type?).to be(true) } end context 'with a semantic or node' do let(:source) do ':foo or 42' end it { expect(or_node.rhs.int_type?).to be(true) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/pair_node_spec.rb000066400000000000000000000446231373365104000224570ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::PairNode do let(:pair_node) { parse_source(source).ast.children.first } describe '.new' do let(:source) { '{ a: 1 }' } it { expect(pair_node.is_a?(described_class)).to be(true) } end describe '#hash_rocket?' do context 'when using a hash rocket delimiter' do let(:source) { '{ a => 1 }' } it { expect(pair_node.hash_rocket?).to be_truthy } end context 'when using a colon delimiter' do let(:source) { '{ a: 1 }' } it { expect(pair_node.hash_rocket?).to be_falsey } end end describe '#colon?' do context 'when using a hash rocket delimiter' do let(:source) { '{ a => 1 }' } it { expect(pair_node.colon?).to be_falsey } end context 'when using a colon delimiter' do let(:source) { '{ a: 1 }' } it { expect(pair_node.colon?).to be_truthy } end end describe '#delimiter' do context 'when using a hash rocket delimiter' do let(:source) { '{ a => 1 }' } it { expect(pair_node.delimiter).to eq('=>') } it { expect(pair_node.delimiter(true)).to eq(' => ') } end context 'when using a colon delimiter' do let(:source) { '{ a: 1 }' } it { expect(pair_node.delimiter).to eq(':') } it { expect(pair_node.delimiter(true)).to eq(': ') } end end describe '#inverse_delimiter' do context 'when using a hash rocket delimiter' do let(:source) { '{ a => 1 }' } it { expect(pair_node.inverse_delimiter).to eq(':') } it { expect(pair_node.inverse_delimiter(true)).to eq(': ') } end context 'when using a colon delimiter' do let(:source) { '{ a: 1 }' } it { expect(pair_node.inverse_delimiter).to eq('=>') } it { expect(pair_node.inverse_delimiter(true)).to eq(' => ') } end end describe '#key' do context 'when using a symbol key' do let(:source) { '{ a: 1 }' } it { expect(pair_node.key.sym_type?).to be(true) } end context 'when using a string key' do let(:source) { "{ 'a' => 1 }" } it { expect(pair_node.key.str_type?).to be(true) } end end describe '#value' do let(:source) { '{ a: 1 }' } it { expect(pair_node.value.int_type?).to be(true) } end describe '#value_on_new_line?' do let(:pair) { parse_source(source).ast.children[0] } context 'when value starts on a new line' do let(:source) do ['{', ' a:', ' 1', '}'].join("\n") end it { expect(pair.value_on_new_line?).to be_truthy } end context 'when value spans multiple lines' do let(:source) do ['{', ' a: (', ' )', '}'].join("\n") end it { expect(pair.value_on_new_line?).to be_falsey } end context 'when pair is on a single line' do let(:source) { "{ 'a' => 1 }" } it { expect(pair.value_on_new_line?).to be_falsey } end end describe '#same_line?' do let(:first_pair) { parse_source(source).ast.children[0] } let(:second_pair) { parse_source(source).ast.children[1] } context 'when both pairs are on the same line' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: 1, b: 2', '}'].join("\n") end it { expect(first_pair.same_line?(second_pair)).to be_truthy } end context 'when both pair is a keyword splat' do let(:source) do ['{', ' a: 1, **foo', '}'].join("\n") end it { expect(first_pair.same_line?(second_pair)).to be_truthy } end end context 'when a multiline pair shares the same line' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: (', ' ), b: 2', '}'].join("\n") end it { expect(first_pair.same_line?(second_pair)).to be_truthy } it { expect(second_pair.same_line?(first_pair)).to be_truthy } end context 'when last pair is a keyword splat' do let(:source) do ['{', ' a: (', ' ), **foo', '}'].join("\n") end it { expect(first_pair.same_line?(second_pair)).to be_truthy } it { expect(second_pair.same_line?(first_pair)).to be_truthy } end end context 'when pairs are on separate lines' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.same_line?(second_pair)).to be_falsey } end context 'when last pair is a keyword splat' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.same_line?(second_pair)).to be_falsey } end end end describe '#key_delta' do let(:first_pair) { parse_source(source).ast.children[0] } let(:second_pair) { parse_source(source).ast.children[1] } context 'with alignment set to :left' do context 'when using colon delimiters' do context 'when keys are aligned' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(0) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(0) } end end context 'when receiver key is behind' do context 'when both pairs are reail pairs' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(-2) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(-2) } end end context 'when receiver key is ahead' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(2) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(2) } end end context 'when both keys are on the same line' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: 1, b: 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(0) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a: 1, **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(0) } end end end context 'when using hash rocket delimiters' do context 'when keys are aligned' do context 'when both keys are explicit keys' do let(:source) do ['{', ' a => 1,', ' b => 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(0) } end context 'when second key is a keyword splat' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(0) } end end context 'when receiver key is behind' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a => 1,', ' b => 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(-2) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(-2) } end end context 'when receiver key is ahead' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a => 1,', ' b => 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(2) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(2) } end end context 'when both keys are on the same line' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a => 1, b => 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(0) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a => 1, **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair)).to eq(0) } end end end end context 'with alignment set to :right' do context 'when using colon delimiters' do context 'when keys are aligned' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end end context 'when receiver key is behind' do context 'when both pairs are reail pairs' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(-2) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end end context 'when receiver key is ahead' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(2) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end end context 'when both keys are on the same line' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: 1, b: 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a: 1, **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end end end context 'when using hash rocket delimiters' do context 'when keys are aligned' do context 'when both keys are explicit keys' do let(:source) do ['{', ' a => 1,', ' b => 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end context 'when second key is a keyword splat' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end end context 'when receiver key is behind' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a => 1,', ' b => 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(-2) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end end context 'when receiver key is ahead' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a => 1,', ' b => 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(2) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end end context 'when both keys are on the same line' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a => 1, b => 2', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a => 1, **foo', '}'].join("\n") end it { expect(first_pair.key_delta(second_pair, :right)).to eq(0) } end end end end end describe '#value_delta' do let(:first_pair) { parse_source(source).ast.children[0] } let(:second_pair) { parse_source(source).ast.children[1] } context 'when using colon delimiters' do context 'when values are aligned' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(0) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(0) } end end context 'when receiver value is behind' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(-2) } end context 'when receiver value is ahead' do let(:source) do ['{', ' a: 1,', ' b: 2', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(2) } end context 'when both pairs are on the same line' do let(:source) do ['{', ' a: 1, b: 2', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(0) } end end context 'when using hash rocket delimiters' do context 'when values are aligned' do context 'when both pairs are explicit pairs' do let(:source) do ['{', ' a => 1,', ' b => 2', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(0) } end context 'when second pair is a keyword splat' do let(:source) do ['{', ' a => 1,', ' **foo', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(0) } end end context 'when receiver value is behind' do let(:source) do ['{', ' a => 1,', ' b => 2', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(-2) } end context 'when receiver value is ahead' do let(:source) do ['{', ' a => 1,', ' b => 2', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(2) } end context 'when both pairs are on the same line' do let(:source) do ['{', ' a => 1, b => 2', '}'].join("\n") end it { expect(first_pair.value_delta(second_pair)).to eq(0) } end end end end rubocop-ast-0.6.0/spec/rubocop/ast/processed_source_spec.rb000066400000000000000000000357531373365104000240720ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ProcessedSource do subject(:processed_source) { described_class.new(source, ruby_version, path) } let(:source) { <<~RUBY } # an awesome method def some_method puts 'foo' end some_method RUBY let(:ast) { processed_source.ast } let(:path) { 'ast/and_node_spec.rb' } shared_context 'invalid encoding source' do let(:source) { "# \xf9" } end describe '.from_file' do describe 'when the file exists' do around do |example| org_pwd = Dir.pwd Dir.chdir("#{__dir__}/..") example.run Dir.chdir(org_pwd) end let(:processed_source) { described_class.from_file(path, ruby_version) } it 'returns an instance of ProcessedSource' do expect(processed_source.is_a?(described_class)).to be(true) end it "sets the file path to the instance's #path" do expect(processed_source.path).to eq(path) end end it 'raises a Errno::ENOENT when the file does not exist' do expect do described_class.from_file('foo', ruby_version) end.to raise_error(Errno::ENOENT) end end describe '#path' do it 'is the path passed to .new' do expect(processed_source.path).to eq(path) end end describe '#buffer' do it 'is a source buffer' do expect(processed_source.buffer.is_a?(Parser::Source::Buffer)).to be(true) end end describe '#ast' do it 'is the root node of AST' do expect(processed_source.ast.is_a?(RuboCop::AST::Node)).to be(true) end end describe '#comments' do it 'is an array of comments' do expect(processed_source.comments.is_a?(Array)).to be(true) expect( processed_source.comments.first.is_a?(Parser::Source::Comment) ).to be(true) end context 'when the source is invalid' do include_context 'invalid encoding source' it 'returns []' do expect(processed_source.comments).to eq [] end end end describe '#tokens' do it 'has an array of tokens' do expect(processed_source.tokens.is_a?(Array)).to be(true) expect(processed_source.tokens.first.is_a?(RuboCop::AST::Token)).to be(true) end end describe '#parser_error' do context 'when the source was properly parsed' do it 'is nil' do expect(processed_source.parser_error.nil?).to be(true) end end context 'when the source lacks encoding comment and is really utf-8 ' \ 'encoded but has been read as US-ASCII' do let(:source) do # When files are read into RuboCop, the encoding of source code # lacking an encoding comment will default to the external encoding, # which could for example be US-ASCII if the LC_ALL environment # variable is set to "C". (+'号码 = 3').force_encoding('US-ASCII') end it 'is nil' do # ProcessedSource#parse sets UTF-8 as default encoding, so no error. expect(processed_source.parser_error.nil?).to be(true) end end context 'when the source could not be parsed due to encoding error' do include_context 'invalid encoding source' it 'returns the error' do expect(processed_source.parser_error.is_a?(Exception)).to be(true) expect(processed_source.parser_error.message) .to include('invalid byte sequence') end end end describe '#lines' do it 'is an array' do expect(processed_source.lines.is_a?(Array)).to be(true) end it 'has same number of elements as line count' do # Since the source has a trailing newline, there is a final empty line expect(processed_source.lines.size).to eq(6) end it 'contains lines as string without linefeed' do first_line = processed_source.lines.first expect(first_line).to eq('# an awesome method') end end describe '#[]' do context 'when an index is passed' do it 'returns the line' do expect(processed_source[3]).to eq('end') end end context 'when a range is passed' do it 'returns the array of lines' do expect(processed_source[3..4]).to eq(%w[end some_method]) end end context 'when start index and length are passed' do it 'returns the array of lines' do expect(processed_source[3, 2]).to eq(%w[end some_method]) end end end describe 'valid_syntax?' do subject { processed_source.valid_syntax? } context 'when the source is completely valid' do let(:source) { 'def valid_code; end' } it 'returns true' do expect(processed_source.diagnostics.empty?).to be(true) expect(processed_source.valid_syntax?).to be(true) end end context 'when the source is invalid' do let(:source) { 'def invalid_code; en' } it 'returns false' do expect(processed_source.valid_syntax?).to be(false) end end context 'when the source is valid but has some warning diagnostics' do let(:source) { 'do_something *array' } it 'returns true' do expect(processed_source.diagnostics.empty?).to be(false) expect(processed_source.diagnostics.first.level).to eq(:warning) expect(processed_source.valid_syntax?).to be(true) end end context 'when the source could not be parsed due to encoding error' do include_context 'invalid encoding source' it 'returns false' do expect(processed_source.valid_syntax?).to be(false) end end # https://github.com/whitequark/parser/issues/283 context 'when the source itself is valid encoding but includes strange ' \ 'encoding literals that are accepted by MRI' do let(:source) do 'p "\xff"' end it 'returns true' do expect(processed_source.diagnostics.empty?).to be(true) expect(processed_source.valid_syntax?).to be(true) end end context 'when a line starts with an integer literal' do let(:source) { '1 + 1' } # regression test it 'tokenizes the source correctly' do expect(processed_source.tokens[0].text).to eq '1' end end end context 'with heavily commented source' do let(:source) { <<~RUBY } # comment one [ 1, { a: 2, b: 3 # comment two } ] RUBY describe '#each_comment' do it 'yields all comments' do comments = [] processed_source.each_comment do |item| expect(item.is_a?(Parser::Source::Comment)).to be true comments << item end expect(comments.size).to eq 2 end end describe '#find_comment' do it 'yields correct comment' do comment = processed_source.find_comment do |item| item.text == '# comment two' end expect(comment.text).to eq '# comment two' end it 'yields nil when there is no match' do comment = processed_source.find_comment do |item| item.text == '# comment four' end expect(comment).to eq nil end end describe '#comment_at_line' do it 'returns the comment at the given line number' do expect(processed_source.comment_at_line(1).text).to eq '# comment one' expect(processed_source.comment_at_line(4).text).to eq '# comment two' end it 'returns nil if line has no comment' do expect(processed_source.comment_at_line(3)).to be nil end end describe '#each_comment_in_lines' do it 'yields the comments' do enum = processed_source.each_comment_in_lines(1..4) expect(enum.is_a?(Enumerable)).to be(true) expect(enum.to_a).to eq processed_source.comments expect(processed_source.each_comment_in_lines(2..5).map(&:text)).to eq ['# comment two'] end end describe '#line_with_comment?' do it 'returns true for lines with comments' do expect(processed_source.line_with_comment?(1)).to be true expect(processed_source.line_with_comment?(4)).to be true end it 'returns false for lines without comments' do expect(processed_source.line_with_comment?(2)).to be false expect(processed_source.line_with_comment?(5)).to be false end end describe '#contains_comment?' do subject(:commented) { processed_source.contains_comment?(range) } let(:array) { ast } let(:hash) { array.children[1] } context 'provided source_range on line without comment' do let(:range) { hash.pairs.first.loc.expression } it { is_expected.to be false } end context 'provided source_range on comment line' do let(:range) { processed_source.find_token(&:comment?).pos } it { is_expected.to be true } end context 'provided source_range on line with comment' do let(:range) { hash.pairs.last.loc.expression } it { is_expected.to be true } end context 'provided a multiline source_range with at least one line with comment' do let(:range) { array.loc.expression } it { is_expected.to be true } end end describe '#comments_before_line' do let(:source) { <<~RUBY } # comment one # comment two [ 1, 2 ] # comment three RUBY it 'returns comments on or before given line' do expect(processed_source.comments_before_line(1).size).to eq 1 expect(processed_source.comments_before_line(2).size).to eq 2 expect(processed_source.comments_before_line(3).size).to eq 2 expect(processed_source.comments_before_line(4).size).to eq 3 expect(processed_source.comments_before_line(1) .first .is_a?(Parser::Source::Comment)).to be true end end end context 'token enumerables' do let(:source) { <<~RUBY } foo(1, 2) RUBY describe '#each_token' do it 'yields all tokens' do tokens = [] processed_source.each_token do |item| expect(item.is_a?(RuboCop::AST::Token)).to be true tokens << item end expect(tokens.size).to eq 7 end end describe '#find_token' do it 'yields correct token' do token = processed_source.find_token(&:comma?) expect(token.text).to eq ',' end it 'yields nil when there is no match' do token = processed_source.find_token(&:right_bracket?) expect(token).to eq nil end end end describe '#file_path' do it 'returns file path' do expect(processed_source.file_path).to eq path end end describe '#blank?' do context 'with source of no content' do let(:source) { <<~RUBY } RUBY it 'returns true' do expect(processed_source.blank?).to eq true end end context 'with source with content' do let(:source) { <<~RUBY } foo RUBY it 'returns false' do expect(processed_source.blank?).to eq false end end end describe '#start_with?' do context 'with blank source' do let(:source) { <<~RUBY } RUBY it 'returns false' do expect(processed_source.start_with?('start')).to eq false expect(processed_source.start_with?('#')).to eq false expect(processed_source.start_with?('')).to eq false end end context 'with present source' do let(:source) { <<~RUBY } foo RUBY it 'returns true when passed string that starts source' do expect(processed_source.start_with?('foo')).to eq true expect(processed_source.start_with?('f')).to eq true expect(processed_source.start_with?('')).to eq true end it 'returns false when passed string that does not start source' do expect(processed_source.start_with?('bar')).to eq false expect(processed_source.start_with?('qux')).to eq false expect(processed_source.start_with?('1')).to eq false end end end describe '#preceding_line' do let(:source) { <<~RUBY } [ line, 1 ] { line: 2 } # line 3 RUBY it 'returns source of line before token' do brace_token = processed_source.find_token(&:left_brace?) expect(processed_source.preceding_line(brace_token)).to eq '[ line, 1 ]' comment_token = processed_source.find_token(&:comment?) expect(processed_source.preceding_line(comment_token)).to eq '{ line: 2 }' end end describe '#following_line' do let(:source) { <<~RUBY } [ line, 1 ] { line: 2 } # line 3 RUBY it 'returns source of line after token' do bracket_token = processed_source.find_token(&:right_bracket?) expect(processed_source.following_line(bracket_token)).to eq '{ line: 2 }' brace_token = processed_source.find_token(&:left_brace?) expect(processed_source.following_line(brace_token)).to eq '# line 3' end end describe '#tokens_within' do let(:source) { <<~RUBY } foo(1, 2) bar(3) RUBY it 'returns tokens for node' do node = ast.children[1] tokens = processed_source.tokens_within(node.source_range) expect(tokens.map(&:text)).to eq(['bar', '(', '3', ')']) end it 'accepts Node as an argument' do node = ast.children[1] tokens = processed_source.tokens_within(node) expect(tokens.map(&:text)).to eq(['bar', '(', '3', ')']) end context 'when heredoc as argument is present' do let(:source) { <<~RUBY } foo(1, [before], <<~DOC, [after]) inside heredoc. DOC bar(2) RUBY it 'returns tokens for node before heredoc' do node = ast.children[0].arguments[1] tokens = processed_source.tokens_within(node.source_range) expect(tokens.map(&:text)).to eq(['[', 'before', ']']) end it 'returns tokens for heredoc node' do node = ast.children[0].arguments[2] tokens = processed_source.tokens_within(node.source_range) expect(tokens.map(&:text)).to eq(['<<"']) end it 'returns tokens for node after heredoc' do node = ast.children[0].arguments[3] tokens = processed_source.tokens_within(node.source_range) expect(tokens.map(&:text)).to eq(['[', 'after', ']']) end end end describe '#first_token_of' do let(:source) { <<~RUBY } foo(1, 2) bar(3) RUBY it 'returns first token for node' do node = ast.children[1] expect(processed_source.first_token_of(node.source_range).text).to eq('bar') end it 'accepts Node as an argument' do node = ast.children[1] expect(processed_source.first_token_of(node).text).to eq('bar') end end describe '#last_token_of' do let(:source) { <<~RUBY } foo(1, 2) bar = baz RUBY it 'returns last token for node' do node = ast.children[1] expect(processed_source.last_token_of(node.source_range).text).to eq('baz') end it 'accepts Node as an argument' do node = ast.children[1] expect(processed_source.last_token_of(node).text).to eq('baz') end end end rubocop-ast-0.6.0/spec/rubocop/ast/range_node_spec.rb000066400000000000000000000021141373365104000226050ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::RangeNode do let(:range_node) { parse_source(source).ast } describe '.new' do context 'with an inclusive range' do let(:source) do '1..2' end it { expect(range_node.is_a?(described_class)).to be(true) } it { expect(range_node.range_type?).to be(true) } end context 'with an exclusive range' do let(:source) do '1...2' end it { expect(range_node.is_a?(described_class)).to be(true) } it { expect(range_node.range_type?).to be(true) } end context 'with an infinite range' do let(:ruby_version) { 2.6 } let(:source) do '1..' end it { expect(range_node.is_a?(described_class)).to be(true) } it { expect(range_node.range_type?).to be(true) } end context 'with a beignless range' do let(:ruby_version) { 2.7 } let(:source) do '..42' end it { expect(range_node.is_a?(described_class)).to be(true) } it { expect(range_node.range_type?).to be(true) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/regexp_node_spec.rb000066400000000000000000000316141373365104000230120ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::RegexpNode do let(:regexp_node) { parse_source(source).ast } describe '.new' do let(:source) { '/re/' } it { expect(regexp_node.is_a?(described_class)).to be(true) } end describe '#to_regexp' do # rubocop:disable Security/Eval context 'with an empty regexp' do let(:source) { '//' } it { expect(regexp_node.to_regexp).to eq(eval(source)) } end context 'with a regexp without option' do let(:source) { '/.+/' } it { expect(regexp_node.to_regexp).to eq(eval(source)) } end context 'with a multi-line regexp without option' do let(:source) { "/\n.+\n/" } it { expect(regexp_node.to_regexp).to eq(eval(source)) } end context 'with an empty regexp with option' do let(:source) { '//ix' } it { expect(regexp_node.to_regexp).to eq(eval(source)) } end context 'with a regexp with option' do let(:source) { '/.+/imx' } it { expect(regexp_node.to_regexp).to eq(eval(source)) } end context 'with a multi-line regexp with option' do let(:source) { "/\n.+\n/ix" } it { expect(regexp_node.to_regexp).to eq(eval(source)) } end # rubocop:enable Security/Eval context 'with a regexp with an "o" option' do let(:source) { '/abc/io' } it { expect(regexp_node.to_regexp.inspect).to eq('/abc/i') } end end describe '#regopt' do let(:regopt) { regexp_node.regopt } context 'with an empty regexp' do let(:source) { '//' } it { expect(regopt.regopt_type?).to be(true) } it { expect(regopt.children.empty?).to be(true) } end context 'with a regexp without option' do let(:source) { '/.+/' } it { expect(regopt.regopt_type?).to be(true) } it { expect(regopt.children.empty?).to be(true) } end context 'with a multi-line regexp without option' do let(:source) { "/\n.+\n/" } it { expect(regopt.regopt_type?).to be(true) } it { expect(regopt.children.empty?).to be(true) } end context 'with an empty regexp with option' do let(:source) { '//ix' } it { expect(regopt.regopt_type?).to be(true) } it { expect(regopt.children).to eq(%i[i x]) } end context 'with a regexp with option' do let(:source) { '/.+/imx' } it { expect(regopt.regopt_type?).to be(true) } it { expect(regopt.children).to eq(%i[i m x]) } end context 'with a multi-line regexp with option' do let(:source) { "/\n.+\n/imx" } it { expect(regopt.regopt_type?).to be(true) } it { expect(regopt.children).to eq(%i[i m x]) } end end describe '#options' do let(:actual_options) { regexp_node.options } # rubocop:disable Security/Eval let(:expected_options) { eval(source).options } # rubocop:enable Security/Eval context 'with an empty regexp' do let(:source) { '//' } it { expect(actual_options).to eq(expected_options) } end context 'with a regexp without option' do let(:source) { '/.+/' } it { expect(actual_options).to eq(expected_options) } end context 'with a regexp with single option' do let(:source) { '/.+/i' } it { expect(actual_options).to eq(expected_options) } end context 'with a regexp with multiple options' do let(:source) { '/.+/ix' } it { expect(actual_options).to eq(expected_options) } end context 'with a regexp with "o" option' do let(:source) { '/.+/o' } it { expect(actual_options).to eq(expected_options) } end end describe '#content' do let(:content) { regexp_node.content } context 'with an empty regexp' do let(:source) { '//' } it { expect(content).to eq('') } end context 'with a regexp without option' do let(:source) { '/.+/' } it { expect(content).to eq('.+') } end context 'with a multi-line regexp without option' do let(:source) { "/\n.+\n/" } it { expect(content).to eq("\n.+\n") } end context 'with an empty regexp with option' do let(:source) { '//ix' } it { expect(content).to eq('') } end context 'with a regexp with option' do let(:source) { '/.+/imx' } it { expect(content).to eq('.+') } end context 'with a multi-line regexp with option' do let(:source) { "/\n.+\n/imx" } it { expect(content).to eq("\n.+\n") } end end describe '#slash_literal?' do context 'with /-delimiters' do let(:source) { '/abc/' } it { expect(regexp_node.slash_literal?).to eq(true) } end context 'with %r/-delimiters' do let(:source) { '%r/abc/' } it { expect(regexp_node.slash_literal?).to eq(false) } end context 'with %r{-delimiters' do let(:source) { '%r{abc}' } it { expect(regexp_node.slash_literal?).to eq(false) } end context 'with multi-line %r{-delimiters' do let(:source) do <<~SRC %r{ abc }x SRC end it { expect(regexp_node.slash_literal?).to eq(false) } end context 'with %r<-delimiters' do let(:source) { '%rx' } it { expect(regexp_node.slash_literal?).to eq(false) } end end describe '#percent_r_literal?' do context 'with /-delimiters' do let(:source) { '/abc/' } it { expect(regexp_node.percent_r_literal?).to eq(false) } end context 'with %r/-delimiters' do let(:source) { '%r/abc/' } it { expect(regexp_node.percent_r_literal?).to eq(true) } end context 'with %r{-delimiters' do let(:source) { '%r{abc}' } it { expect(regexp_node.percent_r_literal?).to eq(true) } end context 'with multi-line %r{-delimiters' do let(:source) do <<~SRC %r{ abc }x SRC end it { expect(regexp_node.percent_r_literal?).to eq(true) } end context 'with %r<-delimiters' do let(:source) { '%rx' } it { expect(regexp_node.percent_r_literal?).to eq(true) } end end describe '#delimiters' do context 'with /-delimiters' do let(:source) { '/abc/' } it { expect(regexp_node.delimiters).to eq(['/', '/']) } end context 'with %r/-delimiters' do let(:source) { '%r/abc/' } it { expect(regexp_node.delimiters).to eq(['/', '/']) } end context 'with %r{-delimiters' do let(:source) { '%r{abc}' } it { expect(regexp_node.delimiters).to eq(['{', '}']) } end context 'with multi-line %r{-delimiters' do let(:source) do <<~SRC %r{ abc }x SRC end it { expect(regexp_node.delimiters).to eq(['{', '}']) } end context 'with %r<-delimiters' do let(:source) { '%rx' } it { expect(regexp_node.delimiters).to eq(['<', '>']) } end end describe '#delimiter?' do context 'with /-delimiters' do let(:source) { '/abc/' } it { expect(regexp_node.delimiter?('/')).to eq(true) } it { expect(regexp_node.delimiter?('{')).to eq(false) } end context 'with %r/-delimiters' do let(:source) { '%r/abc/' } it { expect(regexp_node.delimiter?('/')).to eq(true) } it { expect(regexp_node.delimiter?('{')).to eq(false) } it { expect(regexp_node.delimiter?('}')).to eq(false) } it { expect(regexp_node.delimiter?('%')).to eq(false) } it { expect(regexp_node.delimiter?('r')).to eq(false) } it { expect(regexp_node.delimiter?('%r')).to eq(false) } it { expect(regexp_node.delimiter?('%r/')).to eq(false) } end context 'with %r{-delimiters' do let(:source) { '%r{abc}' } it { expect(regexp_node.delimiter?('{')).to eq(true) } it { expect(regexp_node.delimiter?('}')).to eq(true) } it { expect(regexp_node.delimiter?('/')).to eq(false) } it { expect(regexp_node.delimiter?('%')).to eq(false) } it { expect(regexp_node.delimiter?('r')).to eq(false) } it { expect(regexp_node.delimiter?('%r')).to eq(false) } it { expect(regexp_node.delimiter?('%r/')).to eq(false) } it { expect(regexp_node.delimiter?('%r{')).to eq(false) } end context 'with multi-line %r{-delimiters' do let(:source) do <<~SRC %r{ abc }x SRC end it { expect(regexp_node.delimiter?('{')).to eq(true) } it { expect(regexp_node.delimiter?('}')).to eq(true) } it { expect(regexp_node.delimiter?('/')).to eq(false) } it { expect(regexp_node.delimiter?('%')).to eq(false) } it { expect(regexp_node.delimiter?('r')).to eq(false) } it { expect(regexp_node.delimiter?('%r')).to eq(false) } it { expect(regexp_node.delimiter?('%r/')).to eq(false) } it { expect(regexp_node.delimiter?('%r{')).to eq(false) } end context 'with %r<-delimiters' do let(:source) { '%rx' } it { expect(regexp_node.delimiter?('<')).to eq(true) } it { expect(regexp_node.delimiter?('>')).to eq(true) } it { expect(regexp_node.delimiter?('{')).to eq(false) } it { expect(regexp_node.delimiter?('}')).to eq(false) } it { expect(regexp_node.delimiter?('/')).to eq(false) } it { expect(regexp_node.delimiter?('%')).to eq(false) } it { expect(regexp_node.delimiter?('r')).to eq(false) } it { expect(regexp_node.delimiter?('%r')).to eq(false) } it { expect(regexp_node.delimiter?('%r/')).to eq(false) } it { expect(regexp_node.delimiter?('%r{')).to eq(false) } it { expect(regexp_node.delimiter?('%r<')).to eq(false) } end end describe '#interpolation?' do context 'with direct variable interpoation' do let(:source) { '/\n\n#{foo}(abc)+/' } it { expect(regexp_node.interpolation?).to eq(true) } end context 'with regexp quote' do let(:source) { '/\n\n#{Regexp.quote(foo)}(abc)+/' } it { expect(regexp_node.interpolation?).to eq(true) } end context 'with no interpolation returns false' do let(:source) { '/a{3,6}/' } it { expect(regexp_node.interpolation?).to eq(false) } end end describe '#multiline_mode?' do context 'with no options' do let(:source) { '/x/' } it { expect(regexp_node.multiline_mode?).to be(false) } end context 'with other options' do let(:source) { '/x/ix' } it { expect(regexp_node.multiline_mode?).to be(false) } end context 'with only m option' do let(:source) { '/x/m' } it { expect(regexp_node.multiline_mode?).to be(true) } end context 'with m and other options' do let(:source) { '/x/imx' } it { expect(regexp_node.multiline_mode?).to be(true) } end end describe '#extended?' do context 'with no options' do let(:source) { '/x/' } it { expect(regexp_node.extended?).to be(false) } end context 'with other options' do let(:source) { '/x/im' } it { expect(regexp_node.extended?).to be(false) } end context 'with only x option' do let(:source) { '/x/x' } it { expect(regexp_node.extended?).to be(true) } end context 'with x and other options' do let(:source) { '/x/ixm' } it { expect(regexp_node.extended?).to be(true) } end end describe '#ignore_case?' do context 'with no options' do let(:source) { '/x/' } it { expect(regexp_node.ignore_case?).to be(false) } end context 'with other options' do let(:source) { '/x/xm' } it { expect(regexp_node.ignore_case?).to be(false) } end context 'with only i option' do let(:source) { '/x/i' } it { expect(regexp_node.ignore_case?).to be(true) } end context 'with i and other options' do let(:source) { '/x/xim' } it { expect(regexp_node.ignore_case?).to be(true) } end end describe '#no_encoding?' do context 'with no options' do let(:source) { '/x/' } it { expect(regexp_node.no_encoding?).to be(false) } end context 'with other options' do let(:source) { '/x/xm' } it { expect(regexp_node.no_encoding?).to be(false) } end context 'with only n option' do let(:source) { '/x/n' } it { expect(regexp_node.no_encoding?).to be(true) } end context 'with n and other options' do let(:source) { '/x/xnm' } it { expect(regexp_node.no_encoding?).to be(true) } end end describe '#single_interpolation?' do context 'with no options' do let(:source) { '/x/' } it { expect(regexp_node.single_interpolation?).to be(false) } end context 'with other options' do let(:source) { '/x/xm' } it { expect(regexp_node.single_interpolation?).to be(false) } end context 'with only o option' do let(:source) { '/x/o' } it { expect(regexp_node.single_interpolation?).to be(true) } end context 'with o and other options' do let(:source) { '/x/xom' } it { expect(regexp_node.single_interpolation?).to be(true) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/resbody_node_spec.rb000066400000000000000000000045441373365104000231710ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ResbodyNode do let(:resbody_node) do begin_node = parse_source(source).ast rescue_node, = *begin_node rescue_node.children[1] end describe '.new' do let(:source) { 'begin; beginbody; rescue; rescuebody; end' } it { expect(resbody_node.is_a?(described_class)).to be(true) } end describe '#exceptions' do context 'without exception' do let(:source) { <<~RUBY } begin rescue end RUBY it { expect(resbody_node.exceptions.size).to eq(0) } end context 'with a single exception' do let(:source) { <<~RUBY } begin rescue FooError end RUBY it { expect(resbody_node.exceptions.size).to eq(1) } it { expect(resbody_node.exceptions).to all(be_const_type) } end context 'with multiple exceptions' do let(:source) { <<~RUBY } begin rescue FooError, BarError end RUBY it { expect(resbody_node.exceptions.size).to eq(2) } it { expect(resbody_node.exceptions).to all(be_const_type) } end end describe '#exception_variable' do context 'for an explicit rescue' do let(:source) { 'begin; beginbody; rescue Error => ex; rescuebody; end' } it { expect(resbody_node.exception_variable.source).to eq('ex') } end context 'for an implicit rescue' do let(:source) { 'begin; beginbody; rescue => ex; rescuebody; end' } it { expect(resbody_node.exception_variable.source).to eq('ex') } end context 'when an exception variable is not given' do let(:source) { 'begin; beginbody; rescue; rescuebody; end' } it { expect(resbody_node.exception_variable).to be(nil) } end end describe '#body' do let(:source) { 'begin; beginbody; rescue Error => ex; :rescuebody; end' } it { expect(resbody_node.body.sym_type?).to be(true) } end describe '#branch_index' do let(:source) { <<~RUBY } begin rescue FooError then foo rescue BarError, BazError then bar_and_baz rescue QuuxError => e then quux end RUBY let(:resbodies) { parse_source(source).ast.children.first.resbody_branches } it { expect(resbodies[0].branch_index).to eq(0) } it { expect(resbodies[1].branch_index).to eq(1) } it { expect(resbodies[2].branch_index).to eq(2) } end end rubocop-ast-0.6.0/spec/rubocop/ast/rescue_node_spec.rb000066400000000000000000000054041373365104000230040ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::RescueNode do let(:ast) { parse_source(source).ast } let(:rescue_node) { ast.children.first } describe '.new' do let(:source) { <<~RUBY } begin rescue => e end RUBY it { expect(rescue_node.is_a?(described_class)).to be(true) } end describe '#body' do let(:source) { <<~RUBY } begin foo rescue => e end RUBY it { expect(rescue_node.body.send_type?).to be(true) } end describe '#resbody_branches' do let(:source) { <<~RUBY } begin rescue FooError then foo rescue BarError, BazError then bar_and_baz end RUBY it { expect(rescue_node.resbody_branches.size).to eq(2) } it { expect(rescue_node.resbody_branches).to all(be_resbody_type) } end describe '#branches' do context 'when there is an else' do let(:source) { <<~RUBY } begin rescue FooError then foo rescue BarError then # do nothing else 'bar' end RUBY it 'returns all the bodies' do expect(rescue_node.branches).to match [be_send_type, nil, be_str_type] end context 'with an empty else' do let(:source) { <<~RUBY } begin rescue FooError then foo rescue BarError then # do nothing else # do nothing end RUBY it 'returns all the bodies' do expect(rescue_node.branches).to match [be_send_type, nil, nil] end end end context 'when there is no else keyword' do let(:source) { <<~RUBY } begin rescue FooError then foo rescue BarError then # do nothing end RUBY it 'returns only then rescue bodies' do expect(rescue_node.branches).to match [be_send_type, nil] end end end describe '#else_branch' do context 'without an else statement' do let(:source) { <<~RUBY } begin rescue FooError then foo end RUBY it { expect(rescue_node.else_branch.nil?).to be(true) } end context 'with an else statement' do let(:source) { <<~RUBY } begin rescue FooError then foo else bar end RUBY it { expect(rescue_node.else_branch.send_type?).to be(true) } end end describe '#else?' do context 'without an else statement' do let(:source) { <<~RUBY } begin rescue FooError then foo end RUBY it { expect(rescue_node.else?).to be_falsey } end context 'with an else statement' do let(:source) { <<~RUBY } begin rescue FooError then foo else bar end RUBY it { expect(rescue_node.else?).to be_truthy } end end end rubocop-ast-0.6.0/spec/rubocop/ast/return_node_spec.rb000066400000000000000000000002561373365104000230350ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'wrapped_arguments_node' RSpec.describe RuboCop::AST::ReturnNode do it_behaves_like 'wrapped arguments node', 'return' end rubocop-ast-0.6.0/spec/rubocop/ast/rubocop_compatibility_spec.rb000066400000000000000000000012211373365104000251040ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::RuboCopCompatibility do # rubocop:disable RSpec/FilePath subject(:callback) { RuboCop::AST.rubocop_loaded } before do stub_const '::RuboCop::Version::STRING', rubocop_version end context 'when ran from an incompatible version of Rubocop' do let(:rubocop_version) { '0.42.0' } it 'issues a warning' do expect { callback }.to output(/LineLength/).to_stderr end end context 'when ran from a compatible version of Rubocop' do let(:rubocop_version) { '0.92.0' } it 'issues a warning' do expect { callback }.not_to output.to_stderr end end end rubocop-ast-0.6.0/spec/rubocop/ast/self_class_node_spec.rb000066400000000000000000000017711373365104000236370ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::SelfClassNode do let(:self_class_node) { parse_source(source).ast } describe '.new' do let(:source) do 'class << self; end' end it { expect(self_class_node.is_a?(described_class)).to be(true) } end describe '#identifier' do let(:source) do 'class << self; end' end it { expect(self_class_node.identifier.self_type?).to be(true) } end describe '#body' do context 'with a single expression body' do let(:source) do 'class << self; bar; end' end it { expect(self_class_node.body.send_type?).to be(true) } end context 'with a multi-expression body' do let(:source) do 'class << self; bar; baz; end' end it { expect(self_class_node.body.begin_type?).to be(true) } end context 'with an empty body' do let(:source) do 'class << self; end' end it { expect(self_class_node.body).to be(nil) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/send_node_spec.rb000066400000000000000000000751741373365104000224620ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::SendNode do let(:send_node) { parse_source(source).node } describe '.new' do context 'with a regular method send' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.is_a?(described_class)).to be(true) } end context 'with a safe navigation method send' do let(:source) { 'foo&.bar(:baz)' } it { expect(send_node.is_a?(described_class)).to be(true) } end end describe '#receiver' do context 'with no receiver' do let(:source) { 'bar(:baz)' } it { expect(send_node.receiver.nil?).to be(true) } end context 'with a literal receiver' do let(:source) { "'foo'.bar(:baz)" } it { expect(send_node.receiver.str_type?).to be(true) } end context 'with a variable receiver' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.receiver.send_type?).to be(true) } end end describe '#method_name' do context 'with a plain method' do let(:source) { 'bar(:baz)' } it { expect(send_node.method_name).to eq(:bar) } end context 'with a setter method' do let(:source) { 'foo.bar = :baz' } it { expect(send_node.method_name).to eq(:bar=) } end context 'with an operator method' do let(:source) { 'foo == bar' } it { expect(send_node.method_name).to eq(:==) } end context 'with an implicit call method' do let(:source) { 'foo.(:baz)' } it { expect(send_node.method_name).to eq(:call) } end end describe '#method?' do context 'when message matches' do context 'when argument is a symbol' do let(:source) { 'bar(:baz)' } it { expect(send_node.method?(:bar)).to be_truthy } end context 'when argument is a string' do let(:source) { 'bar(:baz)' } it { expect(send_node.method?('bar')).to be_truthy } end end context 'when message does not match' do context 'when argument is a symbol' do let(:source) { 'bar(:baz)' } it { expect(send_node.method?(:foo)).to be_falsey } end context 'when argument is a string' do let(:source) { 'bar(:baz)' } it { expect(send_node.method?('foo')).to be_falsey } end end end describe '#access_modifier?' do context 'when node is a bare `module_function`' do let(:source) do <<~RUBY module Foo >> module_function << end RUBY end it { expect(send_node.access_modifier?).to be_truthy } end context 'when node is a non-bare `module_function`' do let(:source) do <<~RUBY module Foo >> module_function :foo << end RUBY end it { expect(send_node.access_modifier?).to be_truthy } end context 'when node is not an access modifier' do let(:source) do <<~RUBY module Foo >> some_command << end RUBY end it { expect(send_node.bare_access_modifier?).to be_falsey } end end describe '#bare_access_modifier?' do context 'when node is a bare `module_function`' do let(:source) do <<~RUBY module Foo >> module_function << end RUBY end it { expect(send_node.bare_access_modifier?).to be_truthy } end context 'when node has an argument' do let(:source) do <<~RUBY module Foo >> private :foo << end RUBY end it { expect(send_node.bare_access_modifier?).to be_falsey } end context 'when node is not an access modifier' do let(:source) do <<~RUBY module Foo >> some_command << end RUBY end it { expect(send_node.bare_access_modifier?).to be_falsey } end end describe '#non_bare_access_modifier?' do context 'when node is a non-bare `module_function`' do let(:source) do <<~RUBY module Foo >> module_function :foo << end RUBY end it { expect(send_node.non_bare_access_modifier?).to be_truthy } end context 'when node does not have an argument' do let(:source) do <<~RUBY module Foo >> private << end RUBY end it { expect(send_node.non_bare_access_modifier?).to be_falsey } end context 'when node is not an access modifier' do let(:source) do <<~RUBY module Foo >> some_command << end RUBY end it { expect(send_node.non_bare_access_modifier?).to be_falsey } end end describe '#macro?' do context 'without a receiver' do context 'when parent is a class' do let(:source) do ['class Foo', '>>bar :baz<<', ' bar :qux', 'end'].join("\n") end it { expect(send_node.macro?).to be_truthy } end context 'when parent is a module' do let(:source) do ['module Foo', '>>bar :baz<<', ' bar :qux', 'end'].join("\n") end it { expect(send_node.macro?).to be_truthy } end context 'when parent is a class constructor' do let(:source) do ['Module.new do', '>>bar :baz<<', ' bar :qux', 'end'].join("\n") end it { expect(send_node.macro?).to be_truthy } end context 'when parent is a singleton class' do let(:source) do ['class << self', '>>bar :baz<<', ' bar :qux', 'end'].join("\n") end it { expect(send_node.macro?).to be_truthy } end context 'when parent is a block in a macro scope' do let(:source) do ['concern :Auth do', '>>bar :baz<<', ' bar :qux', 'end'].join("\n") end it { expect(send_node.macro?).to be_truthy } end context 'when parent is a block not in a macro scope' do let(:source) { <<~RUBY } class Foo def bar 3.times do >>something :baz<< other end end end RUBY it { expect(send_node.macro?).to be_falsey } end context 'when in the global scope' do let(:source) { <<~RUBY } >>something :baz<< other RUBY it { expect(send_node.macro?).to be_truthy } end context 'when parent is a keyword begin inside of an class' do let(:source) do ['class Foo', ' begin', '>> bar :qux <<', ' end', 'end'].join("\n") end it { expect(send_node.macro?).to be_truthy } end context 'without a parent' do let(:source) { 'bar :baz' } it { expect(send_node.macro?).to be_truthy } end context 'when parent is a begin without a parent' do let(:source) do ['begin', '>>bar :qux<<', 'end'].join("\n") end it { expect(send_node.macro?).to be_truthy } end context 'when parent is a method definition' do let(:source) do ['def foo', '>>bar :baz<<', 'end'].join("\n") end it { expect(send_node.macro?).to be_falsey } end context 'when in an if' do let(:source) { <<~RUBY } >>bar :baz<< if qux other RUBY it { expect(send_node.macro?).to be_truthy } end context 'when the condition of an if' do let(:source) { <<~RUBY } qux if >>bar :baz<< other RUBY it { expect(send_node.macro?).to be_falsey } end end context 'with a receiver' do context 'when parent is a class' do let(:source) do ['class Foo', ' >> qux.bar :baz <<', 'end'].join("\n") end it { expect(send_node.macro?).to be_falsey } end context 'when parent is a module' do let(:source) do ['module Foo', ' >> qux.bar :baz << ', 'end'].join("\n") end it { expect(send_node.macro?).to be_falsey } end end end describe '#command?' do context 'when argument is a symbol' do context 'with an explicit receiver' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.command?(:bar)).to be_falsey } end context 'with an implicit receiver' do let(:source) { 'bar(:baz)' } it { expect(send_node.command?(:bar)).to be_truthy } end end context 'when argument is a string' do context 'with an explicit receiver' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.command?('bar')).to be_falsey } end context 'with an implicit receiver' do let(:source) { 'bar(:baz)' } it { expect(send_node.command?('bar')).to be_truthy } end end end describe '#arguments' do context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node.arguments.empty?).to be(true) } end context 'with a single literal argument' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.arguments.size).to eq(1) } end context 'with a single splat argument' do let(:source) { 'foo.bar(*baz)' } it { expect(send_node.arguments.size).to eq(1) } end context 'with multiple literal arguments' do let(:source) { 'foo.bar(:baz, :qux)' } it { expect(send_node.arguments.size).to eq(2) } end context 'with multiple mixed arguments' do let(:source) { 'foo.bar(:baz, *qux)' } it { expect(send_node.arguments.size).to eq(2) } end end describe '#first_argument' do context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node.first_argument.nil?).to be(true) } end context 'with a single literal argument' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.first_argument.sym_type?).to be(true) } end context 'with a single splat argument' do let(:source) { 'foo.bar(*baz)' } it { expect(send_node.first_argument.splat_type?).to be(true) } end context 'with multiple literal arguments' do let(:source) { 'foo.bar(:baz, :qux)' } it { expect(send_node.first_argument.sym_type?).to be(true) } end context 'with multiple mixed arguments' do let(:source) { 'foo.bar(:baz, *qux)' } it { expect(send_node.first_argument.sym_type?).to be(true) } end end describe '#last_argument' do context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node.last_argument.nil?).to be(true) } end context 'with a single literal argument' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.last_argument.sym_type?).to be(true) } end context 'with a single splat argument' do let(:source) { 'foo.bar(*baz)' } it { expect(send_node.last_argument.splat_type?).to be(true) } end context 'with multiple literal arguments' do let(:source) { 'foo.bar(:baz, :qux)' } it { expect(send_node.last_argument.sym_type?).to be(true) } end context 'with multiple mixed arguments' do let(:source) { 'foo.bar(:baz, *qux)' } it { expect(send_node.last_argument.splat_type?).to be(true) } end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node.arguments?).to be_falsey } end context 'with a single literal argument' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.arguments?).to be_truthy } end context 'with a single splat argument' do let(:source) { 'foo.bar(*baz)' } it { expect(send_node.arguments?).to be_truthy } end context 'with multiple literal arguments' do let(:source) { 'foo.bar(:baz, :qux)' } it { expect(send_node.arguments?).to be_truthy } end context 'with multiple mixed arguments' do let(:source) { 'foo.bar(:baz, *qux)' } it { expect(send_node.arguments?).to be_truthy } end end describe '#parenthesized?' do context 'with no arguments' do context 'when not using parentheses' do let(:source) { 'foo.bar' } it { expect(send_node.parenthesized?).to be_falsey } end context 'when using parentheses' do let(:source) { 'foo.bar()' } it { expect(send_node.parenthesized?).to be_truthy } end end context 'with arguments' do context 'when not using parentheses' do let(:source) { 'foo.bar :baz' } it { expect(send_node.parenthesized?).to be_falsey } end context 'when using parentheses' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.parenthesized?).to be_truthy } end end end describe '#setter_method?' do context 'with a setter method' do let(:source) { 'foo.bar = :baz' } it { expect(send_node.setter_method?).to be_truthy } end context 'with an indexed setter method' do let(:source) { 'foo.bar[:baz] = :qux' } it { expect(send_node.setter_method?).to be_truthy } end context 'with an operator method' do let(:source) { 'foo.bar + 1' } it { expect(send_node.setter_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.setter_method?).to be_falsey } end end describe '#operator_method?' do context 'with a binary operator method' do let(:source) { 'foo.bar + :baz' } it { expect(send_node.operator_method?).to be_truthy } end context 'with a unary operator method' do let(:source) { '!foo.bar' } it { expect(send_node.operator_method?).to be_truthy } end context 'with a setter method' do let(:source) { 'foo.bar = :baz' } it { expect(send_node.operator_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.operator_method?).to be_falsey } end end describe '#nonmutating_binary_operator_method?' do context 'with a nonmutating binary operator method' do let(:source) { 'foo + bar' } it { expect(send_node.nonmutating_binary_operator_method?).to be_truthy } end context 'with a mutating binary operator method' do let(:source) { 'foo << bar' } it { expect(send_node.nonmutating_binary_operator_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.nonmutating_binary_operator_method?).to be_falsey } end end describe '#nonmutating_unary_operator_method?' do context 'with a nonmutating unary operator method' do let(:source) { '!foo' } it { expect(send_node.nonmutating_unary_operator_method?).to be_truthy } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.nonmutating_unary_operator_method?).to be_falsey } end end describe '#nonmutating_operator_method?' do context 'with a nonmutating binary operator method' do let(:source) { 'foo + bar' } it { expect(send_node.nonmutating_operator_method?).to be_truthy } end context 'with a nonmutating unary operator method' do let(:source) { '!foo' } it { expect(send_node.nonmutating_operator_method?).to be_truthy } end context 'with a mutating binary operator method' do let(:source) { 'foo << bar' } it { expect(send_node.nonmutating_operator_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.nonmutating_operator_method?).to be_falsey } end end describe '#nonmutating_array_method?' do context 'with a nonmutating Array method' do let(:source) { 'array.reverse' } it { expect(send_node.nonmutating_array_method?).to be_truthy } end context 'with a mutating Array method' do let(:source) { 'array.push(foo)' } it { expect(send_node.nonmutating_array_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.nonmutating_array_method?).to be_falsey } end end describe '#nonmutating_hash_method?' do context 'with a nonmutating Hash method' do let(:source) { 'hash.slice(:foo, :bar)' } it { expect(send_node.nonmutating_hash_method?).to be_truthy } end context 'with a mutating Hash method' do let(:source) { 'hash.delete(:foo)' } it { expect(send_node.nonmutating_hash_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.nonmutating_hash_method?).to be_falsey } end end describe '#nonmutating_string_method?' do context 'with a nonmutating String method' do let(:source) { 'string.squeeze' } it { expect(send_node.nonmutating_string_method?).to be_truthy } end context 'with a mutating String method' do let(:source) { 'string.lstrip!' } it { expect(send_node.nonmutating_string_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.nonmutating_string_method?).to be_falsey } end end describe '#comparison_method?' do context 'with a comparison method' do let(:source) { 'foo.bar >= :baz' } it { expect(send_node.comparison_method?).to be_truthy } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.comparison_method?).to be_falsey } end context 'with a negation method' do let(:source) { '!foo' } it { expect(send_node.comparison_method?).to be_falsey } end end describe '#assignment_method?' do context 'with an assignment method' do let(:source) { 'foo.bar = :baz' } it { expect(send_node.assignment_method?).to be_truthy } end context 'with a bracket assignment method' do let(:source) { 'foo.bar[:baz] = :qux' } it { expect(send_node.assignment_method?).to be_truthy } end context 'with a comparison method' do let(:source) { 'foo.bar == :qux' } it { expect(send_node.assignment_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.assignment_method?).to be_falsey } end end describe '#enumerable_method?' do context 'with an enumerable method' do let(:source) { '>> foo.all? << { |e| bar?(e) }' } it { expect(send_node.enumerable_method?).to be_truthy } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.enumerable_method?).to be_falsey } end end describe '#attribute_accessor?' do context 'with an accessor' do let(:source) { 'attr_reader :foo, bar, *baz' } it 'returns the accessor method and Array]' do expect(send_node.attribute_accessor?).to contain_exactly( :attr_reader, contain_exactly( be_sym_type, be_send_type, be_splat_type ) ) end context 'with a call without arguments' do let(:source) { 'attr_reader' } it do expect(send_node.attribute_accessor?).to be(nil) end end end end describe '#dot?' do context 'with a dot' do let(:source) { 'foo.+ 1' } it { expect(send_node.dot?).to be_truthy } end context 'without a dot' do let(:source) { 'foo + 1' } it { expect(send_node.dot?).to be_falsey } end context 'with a double colon' do let(:source) { 'Foo::bar' } it { expect(send_node.dot?).to be_falsey } end context 'with a unary method' do let(:source) { '!foo.bar' } it { expect(send_node.dot?).to be_falsey } end end describe '#double_colon?' do context 'with a double colon' do let(:source) { 'Foo::bar' } it { expect(send_node.double_colon?).to be_truthy } end context 'with a dot' do let(:source) { 'foo.+ 1' } it { expect(send_node.double_colon?).to be_falsey } end context 'without a dot' do let(:source) { 'foo + 1' } it { expect(send_node.double_colon?).to be_falsey } end context 'with a unary method' do let(:source) { '!foo.bar' } it { expect(send_node.double_colon?).to be_falsey } end end describe '#self_receiver?' do context 'with a self receiver' do let(:source) { 'self.bar' } it { expect(send_node.self_receiver?).to be_truthy } end context 'with a non-self receiver' do let(:source) { 'foo.bar' } it { expect(send_node.self_receiver?).to be_falsey } end context 'with an implicit receiver' do let(:source) { 'bar' } it { expect(send_node.self_receiver?).to be_falsey } end end describe '#const_receiver?' do context 'with a self receiver' do let(:source) { 'self.bar' } it { expect(send_node.const_receiver?).to be_falsey } end context 'with a non-constant receiver' do let(:source) { 'foo.bar' } it { expect(send_node.const_receiver?).to be_falsey } end context 'with a constant receiver' do let(:source) { 'Foo.bar' } it { expect(send_node.const_receiver?).to be_truthy } end end describe '#implicit_call?' do context 'with an implicit call method' do let(:source) { 'foo.(:bar)' } it { expect(send_node.implicit_call?).to be_truthy } end context 'with an explicit call method' do let(:source) { 'foo.call(:bar)' } it { expect(send_node.implicit_call?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar' } it { expect(send_node.implicit_call?).to be_falsey } end end describe '#predicate_method?' do context 'with a predicate method' do let(:source) { 'foo.bar?' } it { expect(send_node.predicate_method?).to be_truthy } end context 'with a bang method' do let(:source) { 'foo.bar!' } it { expect(send_node.predicate_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar' } it { expect(send_node.predicate_method?).to be_falsey } end end describe '#bang_method?' do context 'with a bang method' do let(:source) { 'foo.bar!' } it { expect(send_node.bang_method?).to be_truthy } end context 'with a predicate method' do let(:source) { 'foo.bar?' } it { expect(send_node.bang_method?).to be_falsey } end context 'with a regular method' do let(:source) { 'foo.bar' } it { expect(send_node.bang_method?).to be_falsey } end end describe '#camel_case_method?' do context 'with a camel case method' do let(:source) { 'Integer(1.0)' } it { expect(send_node.camel_case_method?).to be_truthy } end context 'with a regular method' do let(:source) { 'integer(1.0)' } it { expect(send_node.camel_case_method?).to be_falsey } end end describe '#block_argument?' do context 'with a block argument' do let(:source) { 'foo.bar(&baz)' } it { expect(send_node.block_argument?).to be_truthy } end context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node.block_argument?).to be_falsey } end context 'with regular arguments' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.block_argument?).to be_falsey } end context 'with mixed arguments' do let(:source) { 'foo.bar(:baz, &qux)' } it { expect(send_node.block_argument?).to be_truthy } end end describe '#block_literal?' do context 'with a block literal' do let(:source) { '>> foo.bar << { |q| baz(q) }' } it { expect(send_node.block_literal?).to be_truthy } end context 'with a block argument' do let(:source) { 'foo.bar(&baz)' } it { expect(send_node.block_literal?).to be_falsey } end context 'with no block' do let(:source) { 'foo.bar' } it { expect(send_node.block_literal?).to be_falsey } end end describe '#arithmetic_operation?' do context 'with a binary arithmetic operation' do let(:source) { 'foo + bar' } it { expect(send_node.arithmetic_operation?).to be_truthy } end context 'with a unary numeric operation' do let(:source) { '+foo' } it { expect(send_node.arithmetic_operation?).to be_falsey } end context 'with a regular method call' do let(:source) { 'foo.bar' } it { expect(send_node.arithmetic_operation?).to be_falsey } end end describe '#block_node' do context 'with a block literal' do let(:source) { '>>foo.bar<< { |q| baz(q) }' } it { expect(send_node.block_node.block_type?).to be(true) } end context 'with a block argument' do let(:source) { 'foo.bar(&baz)' } it { expect(send_node.block_node.nil?).to be(true) } end context 'with no block' do let(:source) { 'foo.bar' } it { expect(send_node.block_node.nil?).to be(true) } end end describe '#splat_argument?' do context 'with a splat argument' do let(:source) { 'foo.bar(*baz)' } it { expect(send_node.splat_argument?).to be_truthy } end context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node.splat_argument?).to be_falsey } end context 'with regular arguments' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.splat_argument?).to be_falsey } end context 'with mixed arguments' do let(:source) { 'foo.bar(:baz, *qux)' } it { expect(send_node.splat_argument?).to be_truthy } end end describe '#def_modifier?' do context 'with a prefixed def modifier' do let(:source) { 'foo def bar; end' } it { expect(send_node.def_modifier?).to be_truthy } end context 'with several prefixed def modifiers' do let(:source) { 'foo bar def baz; end' } it { expect(send_node.def_modifier?).to be_truthy } end end describe '#negation_method?' do context 'with prefix `not`' do let(:source) { 'not foo' } it { expect(send_node.negation_method?).to be_truthy } end context 'with suffix `not`' do let(:source) { 'foo.not' } it { expect(send_node.negation_method?).to be_falsey } end context 'with prefix bang' do let(:source) { '!foo' } it { expect(send_node.negation_method?).to be_truthy } end context 'with a non-negated method' do let(:source) { 'foo.bar' } it { expect(send_node.negation_method?).to be_falsey } end end describe '#prefix_not?' do context 'with keyword `not`' do let(:source) { 'not foo' } it { expect(send_node.prefix_not?).to be_truthy } end context 'with a bang method' do let(:source) { '!foo' } it { expect(send_node.prefix_not?).to be_falsey } end context 'with a non-negated method' do let(:source) { 'foo.bar' } it { expect(send_node.prefix_not?).to be_falsey } end end describe '#prefix_bang?' do context 'with keyword `not`' do let(:source) { 'not foo' } it { expect(send_node.prefix_bang?).to be_falsey } end context 'with a bang method' do let(:source) { '!foo' } it { expect(send_node.prefix_bang?).to be_truthy } end context 'with a non-negated method' do let(:source) { 'foo.bar' } it { expect(send_node.prefix_bang?).to be_falsey } end end describe '#lambda?' do context 'with a lambda method' do let(:source) { '>> lambda << { |foo| bar(foo) }' } it { expect(send_node.lambda?).to be_truthy } end context 'with a method named lambda in a class' do let(:source) { '>> foo.lambda << { |bar| baz }' } it { expect(send_node.lambda?).to be_falsey } end context 'with a stabby lambda method' do let(:source) { '>> -> << (foo) { do_something(foo) }' } it { expect(send_node.lambda?).to be_truthy } end context 'with a non-lambda method' do let(:source) { 'foo.bar' } it { expect(send_node.lambda?).to be_falsey } end end describe '#lambda_literal?' do context 'with a stabby lambda' do let(:source) { '>> -> << (foo) { do_something(foo) }' } it { expect(send_node.lambda_literal?).to be(true) } end context 'with a lambda method' do let(:source) { '>> lambda << { |foo| bar(foo) }' } it { expect(send_node.lambda_literal?).to be(false) } end context 'with a non-lambda method' do let(:source) { 'foo.bar' } it { expect(send_node.lambda?).to be_falsey } end # Regression test https://github.com/rubocop-hq/rubocop/pull/5194 context 'with `a.() {}` style method' do let(:source) { '>>a.()<< {}' } it { expect(send_node.lambda?).to be_falsey } end end describe '#unary_operation?' do context 'with a unary operation' do let(:source) { '-foo' } it { expect(send_node.unary_operation?).to be(true) } end context 'with a binary operation' do let(:source) { 'foo + bar' } it { expect(send_node.unary_operation?).to be(false) } end context 'with a regular method call' do let(:source) { 'foo(bar)' } it { expect(send_node.unary_operation?).to be(false) } end context 'with an implicit call method' do let(:source) { 'foo.(:baz)' } it { expect(send_node.unary_operation?).to be(false) } end end describe '#binary_operation??' do context 'with a unary operation' do let(:source) { '-foo' } it { expect(send_node.binary_operation?).to be(false) } end context 'with a binary operation' do let(:source) { 'foo + bar' } it { expect(send_node.binary_operation?).to be(true) } end context 'with a regular method call' do let(:source) { 'foo(bar)' } it { expect(send_node.binary_operation?).to be(false) } end context 'with an implicit call method' do let(:source) { 'foo.(:baz)' } it { expect(send_node.binary_operation?).to be(false) } end end describe '#post_condition_loop?' do let(:source) { 'foo(bar)' } it { expect(send_node.post_condition_loop?).to be(false) } end describe '#loop_keyword?' do let(:source) { 'foo(bar)' } it { expect(send_node.loop_keyword?).to be(false) } end end rubocop-ast-0.6.0/spec/rubocop/ast/str_node_spec.rb000066400000000000000000000022741373365104000223300ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::StrNode do let(:str_node) { parse_source(source).ast } describe '.new' do context 'with a normal string' do let(:source) { "'foo'" } it { expect(str_node.is_a?(described_class)).to be(true) } end context 'with a string with interpolation' do let(:source) { '"#{foo}"' } it { expect(str_node.is_a?(described_class)).to be(true) } end context 'with a heredoc' do let(:source) do <<~RUBY <<-CODE foo bar CODE RUBY end it { expect(str_node.is_a?(described_class)).to be(true) } end end describe '#heredoc?' do context 'with a normal string' do let(:source) { "'foo'" } it { expect(str_node.heredoc?).to be(false) } end context 'with a string with interpolation' do let(:source) { '"#{foo}"' } it { expect(str_node.heredoc?).to be(false) } end context 'with a heredoc' do let(:source) do <<~RUBY <<-CODE foo bar CODE RUBY end it { expect(str_node.heredoc?).to be(true) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/super_node_spec.rb000066400000000000000000000233371373365104000226610ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::SuperNode do let(:super_node) { parse_source(source).ast } describe '.new' do context 'with a super node' do let(:source) { 'super(:baz)' } it { expect(super_node.is_a?(described_class)).to be(true) } end context 'with a zsuper node' do let(:source) { 'super' } it { expect(super_node.is_a?(described_class)).to be(true) } end end describe '#receiver' do let(:source) { 'super(foo)' } it { expect(super_node.receiver.nil?).to be(true) } end describe '#method_name' do let(:source) { 'super(foo)' } it { expect(super_node.method_name).to eq(:super) } end describe '#method?' do context 'when message matches' do context 'when argument is a symbol' do let(:source) { 'super(:baz)' } it { expect(super_node.method?(:super)).to be_truthy } end context 'when argument is a string' do let(:source) { 'super(:baz)' } it { expect(super_node.method?('super')).to be_truthy } end end context 'when message does not match' do context 'when argument is a symbol' do let(:source) { 'super(:baz)' } it { expect(super_node.method?(:foo)).to be_falsey } end context 'when argument is a string' do let(:source) { 'super(:baz)' } it { expect(super_node.method?('foo')).to be_falsey } end end end describe '#macro?' do let(:super_node) { parse_source(source).ast.children[2] } let(:source) do ['def initialize', ' super(foo)', 'end'].join("\n") end it { expect(super_node.macro?).to be_falsey } end describe '#command?' do context 'when argument is a symbol' do let(:source) { 'super(foo)' } it { expect(super_node.command?(:super)).to be_truthy } end context 'when argument is a string' do let(:source) { 'super(foo)' } it { expect(super_node.command?('super')).to be_truthy } end end describe '#setter_method?' do let(:source) { 'super(foo)' } it { expect(super_node.setter_method?).to be_falsey } end describe '#operator_method?' do let(:source) { 'super(foo)' } it { expect(super_node.operator_method?).to be_falsey } end describe '#comparison_method?' do let(:source) { 'super(foo)' } it { expect(super_node.comparison_method?).to be_falsey } end describe '#assignment_method?' do let(:source) { 'super(foo)' } it { expect(super_node.assignment_method?).to be_falsey } end describe '#dot?' do let(:source) { 'super(foo)' } it { expect(super_node.dot?).to be_falsey } end describe '#double_colon?' do let(:source) { 'super(foo)' } it { expect(super_node.double_colon?).to be_falsey } end describe '#self_receiver?' do let(:source) { 'super(foo)' } it { expect(super_node.self_receiver?).to be_falsey } end describe '#const_receiver?' do let(:source) { 'super(foo)' } it { expect(super_node.const_receiver?).to be_falsey } end describe '#implicit_call?' do let(:source) { 'super(foo)' } it { expect(super_node.implicit_call?).to be_falsey } end describe '#predicate_method?' do let(:source) { 'super(foo)' } it { expect(super_node.predicate_method?).to be_falsey } end describe '#bang_method?' do let(:source) { 'super(foo)' } it { expect(super_node.bang_method?).to be_falsey } end describe '#camel_case_method?' do let(:source) { 'super(foo)' } it { expect(super_node.camel_case_method?).to be_falsey } end describe '#parenthesized?' do context 'with no arguments' do context 'when not using parentheses' do let(:source) { 'super' } it { expect(super_node.parenthesized?).to be_falsey } end context 'when using parentheses' do let(:source) { 'foo.bar()' } it { expect(super_node.parenthesized?).to be_truthy } end end context 'with arguments' do context 'when not using parentheses' do let(:source) { 'foo.bar :baz' } it { expect(super_node.parenthesized?).to be_falsey } end context 'when using parentheses' do let(:source) { 'foo.bar(:baz)' } it { expect(super_node.parenthesized?).to be_truthy } end end end describe '#block_argument?' do context 'with a block argument' do let(:source) { 'super(&baz)' } it { expect(super_node.block_argument?).to be_truthy } end context 'with no arguments' do let(:source) { 'super' } it { expect(super_node.block_argument?).to be_falsey } end context 'with regular arguments' do let(:source) { 'super(:baz)' } it { expect(super_node.block_argument?).to be_falsey } end context 'with mixed arguments' do let(:source) { 'super(:baz, &qux)' } it { expect(super_node.block_argument?).to be_truthy } end end describe '#block_literal?' do context 'with a block literal' do let(:super_node) { parse_source(source).ast.children[0] } let(:source) { 'super { |q| baz(q) }' } it { expect(super_node.block_literal?).to be_truthy } end context 'with a block argument' do let(:source) { 'super(&baz)' } it { expect(super_node.block_literal?).to be_falsey } end context 'with no block' do let(:source) { 'super' } it { expect(super_node.block_literal?).to be_falsey } end end describe '#block_node' do context 'with a block literal' do let(:super_node) { parse_source(source).ast.children[0] } let(:source) { 'super { |q| baz(q) }' } it { expect(super_node.block_node.block_type?).to be(true) } end context 'with a block argument' do let(:source) { 'super(&baz)' } it { expect(super_node.block_node.nil?).to be(true) } end context 'with no block' do let(:source) { 'super' } it { expect(super_node.block_node.nil?).to be(true) } end end describe '#arguments' do context 'with no arguments' do let(:source) { 'super' } it { expect(super_node.arguments.empty?).to be(true) } end context 'with a single literal argument' do let(:source) { 'super(:baz)' } it { expect(super_node.arguments.size).to eq(1) } end context 'with a single splat argument' do let(:source) { 'super(*baz)' } it { expect(super_node.arguments.size).to eq(1) } end context 'with multiple literal arguments' do let(:source) { 'super(:baz, :qux)' } it { expect(super_node.arguments.size).to eq(2) } end context 'with multiple mixed arguments' do let(:source) { 'super(:baz, *qux)' } it { expect(super_node.arguments.size).to eq(2) } end end describe '#first_argument' do context 'with no arguments' do let(:source) { 'super' } it { expect(super_node.first_argument.nil?).to be(true) } end context 'with a single literal argument' do let(:source) { 'super(:baz)' } it { expect(super_node.first_argument.sym_type?).to be(true) } end context 'with a single splat argument' do let(:source) { 'super(*baz)' } it { expect(super_node.first_argument.splat_type?).to be(true) } end context 'with multiple literal arguments' do let(:source) { 'super(:baz, :qux)' } it { expect(super_node.first_argument.sym_type?).to be(true) } end context 'with multiple mixed arguments' do let(:source) { 'superr(:baz, *qux)' } it { expect(super_node.first_argument.sym_type?).to be(true) } end end describe '#last_argument' do context 'with no arguments' do let(:source) { 'super' } it { expect(super_node.last_argument.nil?).to be(true) } end context 'with a single literal argument' do let(:source) { 'super(:baz)' } it { expect(super_node.last_argument.sym_type?).to be(true) } end context 'with a single splat argument' do let(:source) { 'super(*baz)' } it { expect(super_node.last_argument.splat_type?).to be(true) } end context 'with multiple literal arguments' do let(:source) { 'super(:baz, :qux)' } it { expect(super_node.last_argument.sym_type?).to be(true) } end context 'with multiple mixed arguments' do let(:source) { 'super(:baz, *qux)' } it { expect(super_node.last_argument.splat_type?).to be(true) } end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'super' } it { expect(super_node.arguments?).to be_falsey } end context 'with a single literal argument' do let(:source) { 'super(:baz)' } it { expect(super_node.arguments?).to be_truthy } end context 'with a single splat argument' do let(:source) { 'super(*baz)' } it { expect(super_node.arguments?).to be_truthy } end context 'with multiple literal arguments' do let(:source) { 'super(:baz, :qux)' } it { expect(super_node.arguments?).to be_truthy } end context 'with multiple mixed arguments' do let(:source) { 'super(:baz, *qux)' } it { expect(super_node.arguments?).to be_truthy } end end describe '#splat_argument?' do context 'with a splat argument' do let(:source) { 'super(*baz)' } it { expect(super_node.splat_argument?).to be_truthy } end context 'with no arguments' do let(:source) { 'super' } it { expect(super_node.splat_argument?).to be_falsey } end context 'with regular arguments' do let(:source) { 'super(:baz)' } it { expect(super_node.splat_argument?).to be_falsey } end context 'with mixed arguments' do let(:source) { 'super(:baz, *qux)' } it { expect(super_node.splat_argument?).to be_truthy } end end end rubocop-ast-0.6.0/spec/rubocop/ast/symbol_node_spec.rb000066400000000000000000000006531373365104000230240ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::SymbolNode do let(:sym_node) { parse_source(source).ast } describe '.new' do context 'with a symbol node' do let(:source) do ':foo' end it { expect(sym_node.is_a?(described_class)).to be(true) } end end describe '#value' do let(:source) do ':foo' end it { expect(sym_node.value).to eq(:foo) } end end rubocop-ast-0.6.0/spec/rubocop/ast/token_spec.rb000066400000000000000000000264151373365104000216360ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::Token do let(:processed_source) { parse_source(source) } let(:source) { <<~RUBY } # comment def some_method [ 1, 2 ]; foo[0] = 3 end RUBY let(:first_token) { processed_source.tokens.first } let(:comment_token) do processed_source.find_token do |t| t.text.start_with?('#') && t.line == 1 end end let(:left_array_bracket_token) do processed_source.find_token { |t| t.text == '[' && t.line == 3 } end let(:comma_token) { processed_source.find_token { |t| t.text == ',' } } let(:right_array_bracket_token) do processed_source.find_token { |t| t.text == ']' && t.line == 3 } end let(:semicolon_token) { processed_source.find_token { |t| t.text == ';' } } let(:left_ref_bracket_token) do processed_source.find_token { |t| t.text == '[' && t.line == 4 } end let(:zero_token) { processed_source.find_token { |t| t.text == '0' } } let(:right_ref_bracket_token) do processed_source.find_token { |t| t.text == ']' && t.line == 4 } end let(:equals_token) { processed_source.find_token { |t| t.text == '=' } } let(:end_token) { processed_source.find_token { |t| t.text == 'end' } } describe '.from_parser_token' do subject(:token) { described_class.from_parser_token(parser_token) } let(:parser_token) { [type, [text, range]] } let(:type) { :kDEF } let(:text) { 'def' } let(:range) do instance_double(Parser::Source::Range, line: 42, column: 30) end it "sets parser token's type to rubocop token's type" do expect(token.type).to eq(type) end it "sets parser token's text to rubocop token's text" do expect(token.text).to eq(text) end it "sets parser token's range to rubocop token's pos" do expect(token.pos).to eq(range) end it 'returns a #to_s useful for debugging' do expect(token.to_s).to eq('[[42, 30], kDEF, "def"]') end end describe '#line' do it 'returns line of token' do expect(first_token.line).to eq 1 expect(zero_token.line).to eq 4 expect(end_token.line).to eq 5 end end describe '#column' do it 'returns index of first char in token range on that line' do expect(first_token.column).to eq 0 expect(zero_token.column).to eq 6 expect(end_token.column).to eq 0 end end describe '#begin_pos' do it 'returns index of first char in token range of entire source' do expect(first_token.begin_pos).to eq 0 expect(zero_token.begin_pos).to eq 44 expect(end_token.begin_pos).to eq 51 end end describe '#end_pos' do it 'returns index of last char in token range of entire source' do expect(first_token.end_pos).to eq 9 expect(zero_token.end_pos).to eq 45 expect(end_token.end_pos).to eq 54 end end describe '#space_after' do it 'returns truthy MatchData when there is a space after token' do expect(left_array_bracket_token.space_after?.is_a?(MatchData)).to be true expect(right_ref_bracket_token.space_after?.is_a?(MatchData)).to be true expect(left_array_bracket_token.space_after?).to be_truthy expect(right_ref_bracket_token.space_after?).to be_truthy end it 'returns nil when there is not a space after token' do expect(left_ref_bracket_token.space_after?).to be nil expect(zero_token.space_after?).to be nil end end describe '#to_s' do it 'returns string of token data' do expect(end_token.to_s).to include end_token.line.to_s expect(end_token.to_s).to include end_token.column.to_s expect(end_token.to_s).to include end_token.type.to_s expect(end_token.to_s).to include end_token.text.to_s end end describe '#space_before' do it 'returns truthy MatchData when there is a space before token' do expect(left_array_bracket_token.space_before?.is_a?(MatchData)).to be true expect(equals_token.space_before?.is_a?(MatchData)).to be true expect(left_array_bracket_token.space_before?).to be_truthy expect(equals_token.space_before?).to be_truthy end it 'returns nil when there is not a space before token' do expect(semicolon_token.space_before?).to be nil expect(zero_token.space_before?).to be nil end it 'returns nil when it is on the first line' do expect(processed_source.tokens[0].space_before?).to be nil end end context 'type predicates' do describe '#comment?' do it 'returns true for comment tokens' do expect(comment_token.comment?).to be true end it 'returns false for non comment tokens' do expect(zero_token.comment?).to be false expect(semicolon_token.comment?).to be false end end describe '#semicolon?' do it 'returns true for semicolon tokens' do expect(semicolon_token.semicolon?).to be true end it 'returns false for non semicolon tokens' do expect(comment_token.semicolon?).to be false expect(comma_token.semicolon?).to be false end end describe '#left_array_bracket?' do it 'returns true for left_array_bracket tokens' do expect(left_array_bracket_token.left_array_bracket?).to be true end it 'returns false for non left_array_bracket tokens' do expect(left_ref_bracket_token.left_array_bracket?).to be false expect(right_array_bracket_token.left_array_bracket?).to be false end end describe '#left_ref_bracket?' do it 'returns true for left_ref_bracket tokens' do expect(left_ref_bracket_token.left_ref_bracket?).to be true end it 'returns false for non left_ref_bracket tokens' do expect(left_array_bracket_token.left_ref_bracket?).to be false expect(right_ref_bracket_token.left_ref_bracket?).to be false end end describe '#left_bracket?' do it 'returns true for all left_bracket tokens' do expect(left_ref_bracket_token.left_bracket?).to be true expect(left_array_bracket_token.left_bracket?).to be true end it 'returns false for non left_bracket tokens' do expect(right_ref_bracket_token.left_bracket?).to be false expect(right_array_bracket_token.left_bracket?).to be false end end describe '#right_bracket?' do it 'returns true for all right_bracket tokens' do expect(right_ref_bracket_token.right_bracket?).to be true expect(right_array_bracket_token.right_bracket?).to be true end it 'returns false for non right_bracket tokens' do expect(left_ref_bracket_token.right_bracket?).to be false expect(left_array_bracket_token.right_bracket?).to be false end end describe '#left_brace?' do it 'returns true for right_bracket tokens' do expect(right_ref_bracket_token.right_bracket?).to be true expect(right_array_bracket_token.right_bracket?).to be true end it 'returns false for non right_bracket tokens' do expect(left_ref_bracket_token.right_bracket?).to be false expect(left_array_bracket_token.right_bracket?).to be false end end describe '#comma?' do it 'returns true for comma tokens' do expect(comma_token.comma?).to be true end it 'returns false for non comma tokens' do expect(semicolon_token.comma?).to be false expect(right_ref_bracket_token.comma?).to be false end end describe '#rescue_modifier?' do let(:source) { <<~RUBY } def foo bar rescue qux end RUBY let(:rescue_modifier_token) do processed_source.find_token { |t| t.text == 'rescue' } end it 'returns true for rescue modifier tokens' do expect(rescue_modifier_token.rescue_modifier?).to be true end it 'returns false for non rescue modifier tokens' do expect(first_token.rescue_modifier?).to be false expect(end_token.rescue_modifier?).to be false end end describe '#end?' do it 'returns true for end tokens' do expect(end_token.end?).to be true end it 'returns false for non end tokens' do expect(semicolon_token.end?).to be false expect(comment_token.end?).to be false end end describe '#equals_sign?' do it 'returns true for equals sign tokens' do expect(equals_token.equal_sign?).to be true end it 'returns false for non equals sign tokens' do expect(semicolon_token.equal_sign?).to be false expect(comma_token.equal_sign?).to be false end end context 'with braces & parens' do let(:source) { <<~RUBY } { a: 1 } foo { |f| bar(f) } RUBY let(:left_hash_brace_token) do processed_source.find_token { |t| t.text == '{' && t.line == 1 } end let(:right_hash_brace_token) do processed_source.find_token { |t| t.text == '}' && t.line == 1 } end let(:left_block_brace_token) do processed_source.find_token { |t| t.text == '{' && t.line == 2 } end let(:left_parens_token) do processed_source.find_token { |t| t.text == '(' } end let(:right_parens_token) do processed_source.find_token { |t| t.text == ')' } end let(:right_block_brace_token) do processed_source.find_token { |t| t.text == '}' && t.line == 2 } end describe '#left_brace?' do it 'returns true for left hash brace tokens' do expect(left_hash_brace_token.left_brace?).to be true end it 'returns false for non left hash brace tokens' do expect(left_block_brace_token.left_brace?).to be false expect(right_hash_brace_token.left_brace?).to be false end end describe '#left_curly_brace?' do it 'returns true for left block brace tokens' do expect(left_block_brace_token.left_curly_brace?).to be true end it 'returns false for non left block brace tokens' do expect(left_hash_brace_token.left_curly_brace?).to be false expect(right_block_brace_token.left_curly_brace?).to be false end end describe '#right_curly_brace?' do it 'returns true for all right brace tokens' do expect(right_hash_brace_token.right_curly_brace?).to be true expect(right_block_brace_token.right_curly_brace?).to be true end it 'returns false for non right brace tokens' do expect(left_hash_brace_token.right_curly_brace?).to be false expect(left_parens_token.right_curly_brace?).to be false end end describe '#left_parens?' do it 'returns true for left parens tokens' do expect(left_parens_token.left_parens?).to be true end it 'returns false for non left parens tokens' do expect(left_hash_brace_token.left_parens?).to be false expect(right_parens_token.left_parens?).to be false end end describe '#right_parens?' do it 'returns true for right parens tokens' do expect(right_parens_token.right_parens?).to be true end it 'returns false for non right parens tokens' do expect(right_hash_brace_token.right_parens?).to be false expect(left_parens_token.right_parens?).to be false end end end end end rubocop-ast-0.6.0/spec/rubocop/ast/until_node_spec.rb000066400000000000000000000033701373365104000226510ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::UntilNode do let(:until_node) { parse_source(source).ast } describe '.new' do context 'with a statement until' do let(:source) { 'until foo; bar; end' } it { expect(until_node.is_a?(described_class)).to be(true) } end context 'with a modifier until' do let(:source) { 'begin foo; end until bar' } it { expect(until_node.is_a?(described_class)).to be(true) } end end describe '#keyword' do let(:source) { 'until foo; bar; end' } it { expect(until_node.keyword).to eq('until') } end describe '#inverse_keyword' do let(:source) { 'until foo; bar; end' } it { expect(until_node.inverse_keyword).to eq('while') } end describe '#do?' do context 'with a do keyword' do let(:source) { 'until foo do; bar; end' } it { expect(until_node.do?).to be_truthy } end context 'without a do keyword' do let(:source) { 'until foo; bar; end' } it { expect(until_node.do?).to be_falsey } end end describe '#post_condition_loop?' do context 'with a statement until' do let(:source) { 'until foo; bar; end' } it { expect(until_node.post_condition_loop?).to be_falsey } end context 'with a modifier until' do let(:source) { 'begin foo; end until bar' } it { expect(until_node.post_condition_loop?).to be_truthy } end end describe '#loop_keyword?' do context 'with a statement until' do let(:source) { 'until foo; bar; end' } it { expect(until_node.loop_keyword?).to be_truthy } end context 'with a modifier until' do let(:source) { 'begin foo; end until bar' } it { expect(until_node.loop_keyword?).to be_truthy } end end end rubocop-ast-0.6.0/spec/rubocop/ast/when_node_spec.rb000066400000000000000000000052521373365104000224600ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::WhenNode do let(:when_node) { parse_source(source).ast.children[1] } describe '.new' do let(:source) do ['case', 'when :foo then bar', 'end'].join("\n") end it { expect(when_node.is_a?(described_class)).to be(true) } end describe '#conditions' do context 'with a single condition' do let(:source) do ['case', 'when :foo then bar', 'end'].join("\n") end it { expect(when_node.conditions.size).to eq(1) } it { expect(when_node.conditions).to all(be_literal) } end context 'with a multiple conditions' do let(:source) do ['case', 'when :foo, :bar, :baz then bar', 'end'].join("\n") end it { expect(when_node.conditions.size).to eq(3) } it { expect(when_node.conditions).to all(be_literal) } end end describe '#each_condition' do let(:source) do ['case', 'when :foo, :bar, :baz then bar', 'end'].join("\n") end context 'when not passed a block' do it { expect(when_node.each_condition.is_a?(Enumerator)).to be(true) } end context 'when passed a block' do it 'yields all the conditions' do expect { |b| when_node.each_condition(&b) } .to yield_successive_args(*when_node.conditions) end end end describe '#then?' do context 'with a then keyword' do let(:source) do ['case', 'when :foo then bar', 'end'].join("\n") end it { expect(when_node.then?).to be_truthy } end context 'without a then keyword' do let(:source) do ['case', 'when :foo', ' bar', 'end'].join("\n") end it { expect(when_node.then?).to be_falsey } end end describe '#body' do context 'with a then keyword' do let(:source) do ['case', 'when :foo then :bar', 'end'].join("\n") end it { expect(when_node.body.sym_type?).to be(true) } end context 'without a then keyword' do let(:source) do ['case', 'when :foo', ' [:bar, :baz]', 'end'].join("\n") end it { expect(when_node.body.array_type?).to be(true) } end end describe '#branch_index' do let(:source) do ['case', 'when :foo then 1', 'when :bar then 2', 'when :baz then 3', 'end'].join("\n") end let(:whens) { parse_source(source).ast.children[1...-1] } it { expect(whens[0].branch_index).to eq(0) } it { expect(whens[1].branch_index).to eq(1) } it { expect(whens[2].branch_index).to eq(2) } end end rubocop-ast-0.6.0/spec/rubocop/ast/while_node_spec.rb000066400000000000000000000033701373365104000226260ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::WhileNode do let(:while_node) { parse_source(source).ast } describe '.new' do context 'with a statement while' do let(:source) { 'while foo; bar; end' } it { expect(while_node.is_a?(described_class)).to be(true) } end context 'with a modifier while' do let(:source) { 'begin foo; end while bar' } it { expect(while_node.is_a?(described_class)).to be(true) } end end describe '#keyword' do let(:source) { 'while foo; bar; end' } it { expect(while_node.keyword).to eq('while') } end describe '#inverse_keyword' do let(:source) { 'while foo; bar; end' } it { expect(while_node.inverse_keyword).to eq('until') } end describe '#do?' do context 'with a do keyword' do let(:source) { 'while foo do; bar; end' } it { expect(while_node.do?).to be_truthy } end context 'without a do keyword' do let(:source) { 'while foo; bar; end' } it { expect(while_node.do?).to be_falsey } end end describe '#post_condition_loop?' do context 'with a statement while' do let(:source) { 'while foo; bar; end' } it { expect(while_node.post_condition_loop?).to be_falsey } end context 'with a modifier while' do let(:source) { 'begin foo; end while bar' } it { expect(while_node.post_condition_loop?).to be_truthy } end end describe '#loop_keyword?' do context 'with a statement while' do let(:source) { 'while foo; bar; end' } it { expect(while_node.loop_keyword?).to be_truthy } end context 'with a modifier while' do let(:source) { 'begin foo; end while bar' } it { expect(while_node.loop_keyword?).to be_truthy } end end end rubocop-ast-0.6.0/spec/rubocop/ast/wrapped_arguments_node.rb000066400000000000000000000026121373365104000242310ustar00rootroot00000000000000# frozen_string_literal: true RSpec.shared_examples 'wrapped arguments node' do |keyword| let(:return_node) { parse_source(source).ast } describe '.new' do context 'without arguments' do let(:source) { keyword } it { expect(return_node.is_a?(described_class)).to be(true) } end context 'with arguments' do let(:source) { "#{keyword} :foo" } it { expect(return_node.is_a?(described_class)).to be(true) } end end describe '#arguments' do context 'with no arguments' do let(:source) { keyword } it { expect(return_node.arguments.empty?).to be(true) } end context 'with no arguments and braces' do let(:source) { "#{keyword}()" } it { expect(return_node.arguments.empty?).to be(true) } end context 'with a single argument' do let(:source) { "#{keyword} :foo" } it { expect(return_node.arguments.size).to eq(1) } end context 'with a single argument and braces' do let(:source) { "#{keyword}(:foo)" } it { expect(return_node.arguments.size).to eq(1) } end context 'with a single splat argument' do let(:source) { "#{keyword} *baz" } it { expect(return_node.arguments.size).to eq(1) } end context 'with multiple literal arguments' do let(:source) { "#{keyword} :foo, :bar" } it { expect(return_node.arguments.size).to eq(2) } end end end rubocop-ast-0.6.0/spec/rubocop/ast/yield_node_spec.rb000066400000000000000000000203761373365104000226310ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::YieldNode do let(:yield_node) { parse_source(source).ast } describe '.new' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.is_a?(described_class)).to be(true) } end describe '#receiver' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.receiver.nil?).to be(true) } end describe '#method_name' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.method_name).to eq(:yield) } end describe '#method?' do context 'when message matches' do context 'when argument is a symbol' do let(:source) { 'yield :foo' } it { expect(yield_node.method?(:yield)).to be_truthy } end context 'when argument is a string' do let(:source) { 'yield :foo' } it { expect(yield_node.method?('yield')).to be_truthy } end end context 'when message does not match' do context 'when argument is a symbol' do let(:source) { 'yield :bar' } it { expect(yield_node.method?(:foo)).to be_falsey } end context 'when argument is a string' do let(:source) { 'yield :bar' } it { expect(yield_node.method?('foo')).to be_falsey } end end end describe '#macro?' do let(:yield_node) { parse_source(source).ast.children[2] } let(:source) do ['def give_me_bar', ' yield :bar', 'end'].join("\n") end it { expect(yield_node.macro?).to be_falsey } end describe '#command?' do context 'when argument is a symbol' do let(:source) { 'yield :bar' } it { expect(yield_node.command?(:yield)).to be_truthy } end context 'when argument is a string' do let(:source) { 'yield :bar' } it { expect(yield_node.command?('yield')).to be_truthy } end end describe '#arguments' do context 'with no arguments' do let(:source) { 'yield' } it { expect(yield_node.arguments.empty?).to be(true) } end context 'with a single literal argument' do let(:source) { 'yield :foo' } it { expect(yield_node.arguments.size).to eq(1) } end context 'with a single splat argument' do let(:source) { 'yield *foo' } it { expect(yield_node.arguments.size).to eq(1) } end context 'with multiple literal arguments' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.arguments.size).to eq(2) } end context 'with multiple mixed arguments' do let(:source) { 'yield :foo, *bar' } it { expect(yield_node.arguments.size).to eq(2) } end end describe '#first_argument' do context 'with no arguments' do let(:source) { 'yield' } it { expect(yield_node.first_argument.nil?).to be(true) } end context 'with a single literal argument' do let(:source) { 'yield :foo' } it { expect(yield_node.first_argument.sym_type?).to be(true) } end context 'with a single splat argument' do let(:source) { 'yield *foo' } it { expect(yield_node.first_argument.splat_type?).to be(true) } end context 'with multiple literal arguments' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.first_argument.sym_type?).to be(true) } end context 'with multiple mixed arguments' do let(:source) { 'yield :foo, *bar' } it { expect(yield_node.first_argument.sym_type?).to be(true) } end end describe '#last_argument' do context 'with no arguments' do let(:source) { 'yield' } it { expect(yield_node.last_argument.nil?).to be(true) } end context 'with a single literal argument' do let(:source) { 'yield :foo' } it { expect(yield_node.last_argument.sym_type?).to be(true) } end context 'with a single splat argument' do let(:source) { 'yield *foo' } it { expect(yield_node.last_argument.splat_type?).to be(true) } end context 'with multiple literal arguments' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.last_argument.sym_type?).to be(true) } end context 'with multiple mixed arguments' do let(:source) { 'yield :foo, *bar' } it { expect(yield_node.last_argument.splat_type?).to be(true) } end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'yield' } it { expect(yield_node.arguments?).to be_falsey } end context 'with a single literal argument' do let(:source) { 'yield :foo' } it { expect(yield_node.arguments?).to be_truthy } end context 'with a single splat argument' do let(:source) { 'yield *foo' } it { expect(yield_node.arguments?).to be_truthy } end context 'with multiple literal arguments' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.arguments?).to be_truthy } end context 'with multiple mixed arguments' do let(:source) { 'yield :foo, *bar' } it { expect(yield_node.arguments?).to be_truthy } end end describe '#parenthesized?' do context 'with no arguments' do context 'when not using parentheses' do let(:source) { 'yield' } it { expect(yield_node.parenthesized?).to be_falsey } end context 'when using parentheses' do let(:source) { 'yield()' } it { expect(yield_node.parenthesized?).to be_truthy } end end context 'with arguments' do context 'when not using parentheses' do let(:source) { 'yield :foo' } it { expect(yield_node.parenthesized?).to be_falsey } end context 'when using parentheses' do let(:source) { 'yield(:foo)' } it { expect(yield_node.parenthesized?).to be_truthy } end end end describe '#setter_method?' do let(:source) { 'yield :foo' } it { expect(yield_node.setter_method?).to be_falsey } end describe '#operator_method?' do let(:source) { 'yield :foo' } it { expect(yield_node.operator_method?).to be_falsey } end describe '#comparison_method?' do let(:source) { 'yield :foo' } it { expect(yield_node.comparison_method?).to be_falsey } end describe '#assignment_method?' do let(:source) { 'yield :foo' } it { expect(yield_node.assignment_method?).to be_falsey } end describe '#dot?' do let(:source) { 'yield :foo' } it { expect(yield_node.dot?).to be_falsey } end describe '#double_colon?' do let(:source) { 'yield :foo' } it { expect(yield_node.double_colon?).to be_falsey } end describe '#self_receiver?' do let(:source) { 'yield :foo' } it { expect(yield_node.self_receiver?).to be_falsey } end describe '#const_receiver?' do let(:source) { 'yield :foo' } it { expect(yield_node.const_receiver?).to be_falsey } end describe '#implicit_call?' do let(:source) { 'yield :foo' } it { expect(yield_node.implicit_call?).to be_falsey } end describe '#predicate_method?' do let(:source) { 'yield :foo' } it { expect(yield_node.predicate_method?).to be_falsey } end describe '#bang_method?' do let(:source) { 'yield :foo' } it { expect(yield_node.bang_method?).to be_falsey } end describe '#camel_case_method?' do let(:source) { 'yield :foo' } it { expect(yield_node.camel_case_method?).to be_falsey } end describe '#block_argument?' do let(:source) { 'yield :foo' } it { expect(yield_node.block_argument?).to be_falsey } end describe '#block_literal?' do let(:source) { 'yield :foo' } it { expect(yield_node.block_literal?).to be_falsey } end describe '#block_node' do let(:source) { 'yield :foo' } it { expect(yield_node.block_node.nil?).to be(true) } end describe '#splat_argument?' do context 'with a splat argument' do let(:source) { 'yield *foo' } it { expect(yield_node.splat_argument?).to be_truthy } end context 'with no arguments' do let(:source) { 'yield' } it { expect(yield_node.splat_argument?).to be_falsey } end context 'with regular arguments' do let(:source) { 'yield :foo' } it { expect(yield_node.splat_argument?).to be_falsey } end context 'with mixed arguments' do let(:source) { 'yield :foo, *bar' } it { expect(yield_node.splat_argument?).to be_truthy } end end end rubocop-ast-0.6.0/spec/spec_helper.rb000066400000000000000000000040701373365104000175260ustar00rootroot00000000000000# frozen_string_literal: true require 'yaml' $VERBOSE = true if ENV.fetch('COVERAGE', 'f').start_with? 't' require 'simplecov' SimpleCov.start end require 'rubocop-ast' if ENV['MODERNIZE'] RuboCop::AST::Builder.modernize RuboCop::AST::Builder.emit_forward_arg = false # inverse of default end RSpec.shared_context 'ruby 2.3', :ruby23 do let(:ruby_version) { 2.3 } end RSpec.shared_context 'ruby 2.4', :ruby24 do let(:ruby_version) { 2.4 } end RSpec.shared_context 'ruby 2.5', :ruby25 do let(:ruby_version) { 2.5 } end RSpec.shared_context 'ruby 2.6', :ruby26 do let(:ruby_version) { 2.6 } end RSpec.shared_context 'ruby 2.7', :ruby27 do let(:ruby_version) { 2.7 } end # ... module DefaultRubyVersion extend RSpec::SharedContext let(:ruby_version) { 2.4 } end module RuboCop module AST # patch class class ProcessedSource attr_accessor :node end end end # ... module ParseSourceHelper def parse_source(source) lookup = nil ruby = source.gsub(/>>(.*)< { raise "No node corresponds to source '#{lookup}'" } ) { |node| node.source == lookup } else source.ast end source end end RSpec.configure do |config| config.include ParseSourceHelper config.include DefaultRubyVersion config.shared_context_metadata_behavior = :apply_to_host_groups config.filter_run_when_matching :focus config.example_status_persistence_file_path = 'spec/examples.txt' config.disable_monkey_patching! config.warnings = true config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true expectations.syntax = :expect end config.mock_with :rspec do |mocks| mocks.syntax = :expect mocks.verify_partial_doubles = true end config.order = :random Kernel.srand config.seed end rubocop-ast-0.6.0/tasks/000077500000000000000000000000001373365104000151025ustar00rootroot00000000000000rubocop-ast-0.6.0/tasks/check_commit.rake000066400000000000000000000015411373365104000203740ustar00rootroot00000000000000# frozen_string_literal: true begin require 'rubocop/rake_task' rescue LoadError return end require 'English' def commit_paths(commit_range) commit_range = "#{commit_range}~..HEAD" if commit_range.include?('..') `git diff-tree --no-commit-id --name-only -r #{commit_range}`.split("\n") ensure exit($CHILD_STATUS.exitstatus) if $CHILD_STATUS.exitstatus != 0 end desc 'Check files modified in commit (default: HEAD) with rspec and rubocop' RuboCop::RakeTask.new(:check_commit, :commit) do |t, args| commit = args[:commit] || 'HEAD' paths = commit_paths(commit) paths.reject { |p| p.start_with?(/docs|Gemfile|README|CHANGELOG/) } specs = paths.select { |p| p.start_with?('spec') } if specs.empty? puts 'Caution: No spec was changed!' else puts "Checking: #{paths.join(' ')}" system 'rspec', *paths end t.patterns = paths end rubocop-ast-0.6.0/tasks/cut_release.rake000066400000000000000000000030271373365104000202430ustar00rootroot00000000000000# frozen_string_literal: true require 'bump' def update_file(path) content = File.read(path) File.write(path, yield(content)) end namespace :cut_release do %w[major minor patch pre].each do |release_type| desc "Cut a new #{release_type} release, create release notes " \ 'and update documents.' task release_type do run(release_type) end end def version_sans_patch(version) version.split('.').take(2).join('.') end def update_antora(version) update_file('docs/antora.yml') do |yaml| yaml.gsub(/version: .*/, "version: '#{version_sans_patch(version)}'") end end def add_header_to_changelog(version) update_file('CHANGELOG.md') do |changelog| head, tail = changelog.split("## master (unreleased)\n\n", 2) [ head, "## master (unreleased)\n\n", "## #{version} (#{Time.now.strftime('%F')})\n\n", tail ].join end end def run(release_type) old_version = Bump::Bump.current Bump::Bump.run(release_type, commit: false, bundle: false, tag: false) new_version = Bump::Bump.current add_header_to_changelog(new_version) update_antora(new_version) puts "Changed version from #{old_version} to #{new_version}." cmd = "git commit -am 'Cut #{new_version}'" puts cmd system cmd end end desc 'and restore docs/antora' task :release do update_file 'docs/antora.yml' do |s| s.gsub!(/version: .*/, 'version: master') end cmd = "git commit -am 'Restore docs/antora.yml'" puts cmd system cmd end