pax_global_header00006660000000000000000000000064143417222500014512gustar00rootroot0000000000000052 comment=5ef9d88181f1fcb8cf586e9927b221e676ed6066 rubocop-ast-1.24.0/000077500000000000000000000000001434172225000140345ustar00rootroot00000000000000rubocop-ast-1.24.0/.github/000077500000000000000000000000001434172225000153745ustar00rootroot00000000000000rubocop-ast-1.24.0/.github/workflows/000077500000000000000000000000001434172225000174315ustar00rootroot00000000000000rubocop-ast-1.24.0/.github/workflows/rubocop.yml000066400000000000000000000110251434172225000216240ustar00rootroot00000000000000# 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: branches: - master pull_request: jobs: ast_specs: name: >- ${{ matrix.title || 'Specs' }} | ${{ matrix.ruby }} runs-on: ${{ matrix.os }}-latest timeout-minutes: 5 env: # See https://github.com/tmm1/test-queue#environment-variables TEST_QUEUE_WORKERS: 2 RUBOCOP_VERSION: none JRUBY_OPTS: --dev # http://blog.headius.com/2019/09/jruby-startup-time-exploration.html TRUFFLERUBYOPT: --engine.Mode=latency # optimize for short test suites strategy: fail-fast: false matrix: os: [ubuntu] ruby: [2.6, 2.7, "3.0", 3.1, head] coverage: [null] modern: [null] title: [null] include: - { os: windows, ruby: mingw } - { ruby: 2.6, os: ubuntu, coverage: true, title: "Coverage" } - { ruby: "3.0", os: ubuntu, modern: true, title: 'Specs "modern"' } - { ruby: jruby, os: ubuntu } # jruby disabled because of: https://github.com/jruby/jruby/issues/6416 # - { ruby: jruby-head, os: ubuntu } - { ruby: truffleruby, os: ubuntu } - { ruby: truffleruby-head, os: ubuntu } steps: - name: windows misc if: matrix.os == 'windows' run: | # set TMPDIR, git core.autocrlf echo "TMPDIR=$env:RUNNER_TEMP" >> $GITHUB_ENV git config --system core.autocrlf false - name: checkout uses: actions/checkout@v3 - 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: 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 'MODERNIZE=true' >> $GITHUB_ENV - name: independence check if: matrix.os != 'windows' run: (! bundle exec rubocop -h 2> /dev/null) && echo 'RuboCop successfully *not* loaded for local tests' - name: spec if: "matrix.coverage != true && matrix.internal_investigation != true" run: bundle exec rake spec rubocop_specs: name: >- Main Gem Specs | RuboCop: ${{ matrix.rubocop }} | ${{ matrix.ruby }} 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.6, 2.7] rubocop: [master] internal_investigation: [null] include: - { rubocop: master, ruby: "3.0", os: ubuntu } steps: - name: checkout uses: actions/checkout@v3 - name: set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - name: setup env run: echo 'RUBOCOP_VERSION=${{ matrix.rubocop }}' >> $GITHUB_ENV - name: install rubocop from source for full specs run: git clone --branch ${{ matrix.rubocop }} https://github.com/rubocop-hq/rubocop.git ../rubocop - name: install rubocop dependencies run: cd ../rubocop && bundle install --jobs 3 --retry 3 - name: install dependencies run: bundle install --jobs 3 --retry 3 - name: generate lexer and parser run: bundle exec rake generate - name: spec run: cd ../rubocop && bundle exec rake spec internal_investigation: name: >- Coding Style | ${{ matrix.ruby }} runs-on: ${{ matrix.os }}-latest env: # See https://github.com/tmm1/test-queue#environment-variables TEST_QUEUE_WORKERS: 2 RUBOCOP_VERSION: master strategy: fail-fast: false matrix: os: [ubuntu] ruby: ["3.0"] rubocop: [master] steps: - name: checkout uses: actions/checkout@v3 - 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: internal investigation run: bundle exec rake generate internal_investigation rubocop-ast-1.24.0/.github/workflows/spell_checking.yml000066400000000000000000000021161434172225000231260ustar00rootroot00000000000000name: Spell Checking on: [pull_request] jobs: codespell: name: Check spelling of all files with codespell runs-on: ubuntu-latest strategy: matrix: python-version: [3.8] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install codespell if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Check spelling with codespell run: codespell --ignore-words=codespell.txt || exit 1 misspell: name: Check spelling of all files in commit with misspell runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install run: wget -O - -q https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh | sh -s -- -b . - name: Misspell run: git ls-files --empty-directory | xargs ./misspell -error rubocop-ast-1.24.0/.gitignore000066400000000000000000000021371434172225000160270ustar00rootroot00000000000000# generated parser / lexer /lib/rubocop/ast/node_pattern/parser.racc.rb /lib/rubocop/ast/node_pattern/parser.output /lib/rubocop/ast/node_pattern/lexer.rex.rb # 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-1.24.0/.mergify.yml000066400000000000000000000021631434172225000163010ustar00rootroot00000000000000pull_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 | 2.5' - 'status-success=Specs | 2.6' - 'status-success=Specs | 2.7' - 'status-success=Specs | 3.0' - 'status-success=Specs | head' - 'status-success=Specs | mingw' - 'status-success=Coverage | 2.5' - 'status-success=Specs "modern" | 3.0' # - 'status-success=Specs | jruby' - 'status-success=Specs | jruby-head' - 'status-success=Specs | truffleruby' - 'status-success=Specs | truffleruby-head' - 'status-success=Main Gem Specs | RuboCop: master | 2.5' - 'status-success=Main Gem Specs | RuboCop: master | 2.7' - 'status-success=Main Gem Specs | RuboCop: master | 3.0' - 'status-success=Coding Style | 3.0' - name: delete head branch after auto-merge conditions: - merged - label=auto-merge actions: delete_head_branch: {} rubocop-ast-1.24.0/.rspec000066400000000000000000000000361434172225000151500ustar00rootroot00000000000000--color --require spec_helper rubocop-ast-1.24.0/.rubocop.yml000066400000000000000000000047141434172225000163140ustar00rootroot00000000000000# 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/**/*' - 'lib/rubocop/ast/node_pattern/parser.racc.rb' - 'lib/rubocop/ast/node_pattern/lexer.rex.rb' - 'spec/rubocop/ast/node_pattern/parse_helper.rb' - 'spec/rubocop/ast/fixtures/*' TargetRubyVersion: 2.6 SuggestExtensions: false # It cannot be replaced with suggested methods defined by RuboCop AST itself. InternalAffairs/MethodNameEndWith: Enabled: false InternalAffairs/NodeMatcherDirective: Exclude: - 'spec/rubocop/ast/node_pattern_spec.rb' 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: Language: Expectations: - expect_parsing RSpec/PredicateMatcher: Strict: false RSpec/ImplicitSubject: Enabled: false RSpec/MessageSpies: EnforcedStyle: receive RSpec/NestedGroups: Max: 7 RSpec/MultipleMemoizedHelpers: Enabled: false RSpec/FactoryBot/CreateList: Enabled: false RSpec/StubbedMock: Enabled: false rubocop-ast-1.24.0/.rubocop_todo.yml000066400000000000000000000055541434172225000173440ustar00rootroot00000000000000# 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: 108 # 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' - 'spec/rubocop/ast/node_pattern/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' - 'spec/rubocop/ast/node_pattern/parser_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-1.24.0/CHANGELOG.md000066400000000000000000000403471434172225000156550ustar00rootroot00000000000000# Change log ## master (unreleased) ## 1.24.0 (2022-11-30) ### New features * [#245](https://github.com/rubocop/rubocop-ast/pull/245): Add node types `forwarded_restarg` and `forwarded_kwrestarg`. ([@ydah][]) ## 1.23.0 (2022-10-21) ### New features * [#242](https://github.com/rubocop/rubocop-ast/pull/242): Add `character_literal?` to `StrNode`. ([@koic][]) ## 1.22.0 (2022-10-17) ### New features * [#240](https://github.com/rubocop/rubocop-ast/pull/240): Add a type predicate `new_line?` to Token. ([@tdeo][]) ## 1.21.0 (2022-08-08) ### New features * [#231](https://github.com/rubocop/rubocop-ast/pull/231): Add a type predicate `dot?` to Token. ([@nobuyo][]) ## 1.20.1 (2022-08-07) ### New features * [#237](https://github.com/rubocop/rubocop-ast/pull/237) Fix `#macro?` for numblock nodes ([@gsamokovarov][]) ## 1.20.0 (2022-08-07) ### Bug fixes * [#230](https://github.com/rubocop-hq/rubocop-ast/pull/230): Make `RegexpNode` aware of fixed-encoding regopt. ([@koic][]) ## 1.19.1 (2022-07-10) ### New features * [#235](https://github.com/rubocop-hq/rubocop-ast/pull/235): Add `regexp_dots?` method to `RuboCop::AST::Token` (erroneously released in 1.19.0 as `regexp_dot?`). ([@koic][]) ## 1.18.0 (2022-05-13) ### New features * [#233](https://github.com/rubocop-hq/rubocop-ast/pull/233): Make parse from Ruby 1.9 to 2.3 available. ([@koic][]) ### Changes * [#232](https://github.com/rubocop/rubocop-ast/pull/232): **(Compatibility)** Drop support for Ruby 2.5. ([@koic][]) ## 1.17.0 (2022-04-09) ### New features * [#227](https://github.com/rubocop-hq/rubocop-ast/pull/227): Make `Node#condition?` aware of `case-match` node. ([@koic][]) ## 1.16.0 (2022-02-21) ### New features * [#223](https://github.com/rubocop-hq/rubocop-ast/pull/223): Support `Parser::Ruby32` for Ruby 3.2 parser (experimental). ([@koic][]) ## 1.15.2 (2022-02-12) ### Bug fixes * Fix `:&` parsing ([@zverok][]) ## 1.15.1 (2021-12-27) ### Bug fixes * [#10220](https://github.com/rubocop/rubocop/pull/10220): Make `AST::Node#receiver` aware of `csend` block method calls. ([@koic][]) ## 1.15.0 (2021-12-12) ### New features * [#10219](https://github.com/rubocop/rubocop/pull/10219): Add `value_omission` method to `AST::PairNode` for Ruby 3.1's hash value omission. ([@koic][]) ## 1.14.0 (2021-12-02) ### New features * [#218](https://github.com/rubocop/rubocop-ast/pull/218): Support Ruby 3.1's anonymous block forwarding syntax. ([@koic][]) ## 1.13.0 (2021-11-07) ### New features * [#213](https://github.com/rubocop/rubocop-ast/pull/213): Make `Node#numeric_type?` aware of rational and complex literals. ([@koic][]) ## 1.12.0 (2021-09-27) ### Bug fixes * [#208](https://github.com/rubocop/rubocop-ast/issues/208): Update `MethodDispatchNode#block_literal?` to return true for `numblock`s. ([@dvandersluis][]) ## 1.11.0 (2021-08-24) ### New features * [#205](https://github.com/rubocop/rubocop-ast/pull/205): Make class, module, and struct definitions aware of numblock. ([@koic][]) ## 1.10.0 (2021-08-12) ### New features * [#201](https://github.com/rubocop/rubocop-ast/pull/201): Add discrete node classes for assignments. ([@dvandersluis][]) ## 1.9.1 (2021-08-10) ### Bug fixes * [#197](https://github.com/rubocop/rubocop-ast/pull/197): [Fix #184] Fix `Node#parent_module_name` for `sclass` nodes. ([@dvandersluis][]) ## 1.9.0 (2021-08-06) ### New features * [#195](https://github.com/rubocop/rubocop-ast/pull/195): Move `ProcessedSource#sorted_tokens` to be a public method. ([@dvandersluis][]) ## 1.8.0 (2021-07-14) ### New features * [#192](https://github.com/rubocop/rubocop-ast/pull/192): Add `branches` method for `AST::CaseMatchNode`. ([@koic][]) ### Changes * Escape References in Documentation, partially addressing https://github.com/rubocop/rubocop/issues/9150. ([@wcmonty][]) ## 1.7.0 (2021-05-28) ### New features * [#171](https://github.com/rubocop/rubocop-ast/pull/171): Add `SendNode#def_modifier` that returns the `def` node it modifies, or `nil`. ([@marcandre][]) * [#186](https://github.com/rubocop/rubocop-ast/pull/186): Add `pattern` method for `AST::InPatternNode` node. ([@koic][]) ## 1.6.0 (2021-05-26) ### New features * [#183](https://github.com/rubocop/rubocop-ast/pull/183): Add `AST::InPatternNode` node. ([@koic][]) ## 1.5.0 (2021-05-02) ### New features * [#182](https://github.com/rubocop/rubocop-ast/pull/182): Support `Parser::Ruby31` for Ruby 3.1 parser (experimental). ([@koic][]) ## 1.4.2 (2021-05-02) ### Bug fixes * [#179](https://github.com/rubocop/rubocop-ast/pull/179): Have `ast_with_comments` distinguish nodes with same content. ([@marcandre][]) ## 1.4.1 (2021-01-23) ### Changes * [#167](https://github.com/rubocop/rubocop-ast/pull/167): Fix `#value` for `dstr` nodes to return the actual string value. ([@dvandersluis][]) ## 1.4.0 (2021-01-01) ### Changes * [#162](https://github.com/rubocop/rubocop-ast/pull/162): Improve compatibility with `parser` 3.0. Turn on `emit_match_pattern` switch. ([@marcandre][]) ## 1.3.0 (2020-11-30) ### Changes * [#156](https://github.com/rubocop/rubocop-ast/issues/156): NodePattern now considers constant names to refer to constants (instead of predicate `#Example_type?`). ([@marcandre][]) ## 1.2.0 (2020-11-24) ### New features * [#154](https://github.com/rubocop/rubocop-ast/pull/154): Add `ArgNode` and `Procarg0Node` ("modern" mode), and add `ArgsNode#argument_list` to get only argument type nodes. ([@dvandersluis][]) ### Changes * [#155](https://github.com/rubocop/rubocop-ast/pull/155): Enable `BlockNode#argument_list` for `numblock`s. ([@dvandersluis][]) * [#154](https://github.com/rubocop/rubocop-ast/pull/154): Add `BlockNode#argument_list` and `BlockNode#argument_names`. ([@dvandersluis][]) * [#147](https://github.com/rubocop/rubocop-ast/pull/147): `def_node_pattern` and `def_node_search` now return the method name. ([@marcandre][]) ## 1.1.1 (2020-11-04) ### Bug fixes * [#146](https://github.com/rubocop/rubocop-ast/pull/146): Fix `IfNode#branches` to return both branches when called on ternary conditional. ([@fatkodima][]) ## 1.1.0 (2020-10-26) ### New features * [#144](https://github.com/rubocop/rubocop-ast/pull/144): NodePattern: allow method calls on constants. ([@marcandre][]) ## 1.0.1 (2020-10-23) ### Bug fixes * [#141](https://github.com/rubocop/rubocop-ast/pull/141): Make `SendNode#macro?` and `RuboCop::AST::Node#class_constructor?` aware of struct constructor and `RuboCop::AST::Node#struct_constructor?` is deprecated. ([@koic][]) * [#142](https://github.com/rubocop/rubocop-ast/pull/142): Only traverse send nodes in `MethodDispatchNode#def_modifier?`. ([@eugeneius][]) ## 1.0.0 (2020-10-21) ### Changes * None since 0.8; official 1.0 release coinciding with RuboCop 1.0 and API considered stable. ([@marcandre][]) ## 0.8.0 (2020-10-12) ### New features * [#49](https://github.com/rubocop/rubocop-ast/pull/49): Add `DefNode#endless?` (Ruby 3.0). ([@marcandre][]) * [#117](https://github.com/rubocop/rubocop-ast/pull/117): Future-proof `AST::Traversal` by detecting unknown `Node` types. ([@marcandre][]) * [#131](https://github.com/rubocop/rubocop-ast/pull/131): Add rake tasks to merge and create Changelog entries. ([@marcandre][]) ### Bug fixes * [#117](https://github.com/rubocop/rubocop-ast/pull/117): All nodes of `break` and `next` are now traversed. ([@marcandre][]) ## 0.7.1 (2020-09-28) ### Bug fixes * [#127](https://github.com/rubocop/rubocop-ast/pull/127): Fix dependency issue for JRuby. ([@marcandre][]) ## 0.7.0 (2020-09-27) ### New features * [#105](https://github.com/rubocop/rubocop-ast/pull/105): `NodePattern` compiler [complete rewrite](https://docs.rubocop.org/rubocop-ast/node_pattern_compiler.html). Add support for multiple variadic terms. ([@marcandre][]) * [#109](https://github.com/rubocop/rubocop-ast/pull/109): Add `NodePattern` debugging rake tasks: `test_pattern`, `compile`, `parse`. See also [this app](https://nodepattern.herokuapp.com) ([@marcandre][]) * [#110](https://github.com/rubocop/rubocop-ast/pull/110): Add `NodePattern` support for multiple terms unions. ([@marcandre][]) * [#111](https://github.com/rubocop/rubocop-ast/pull/111): Optimize some `NodePattern`s by using `Set`s. ([@marcandre][]) * [#112](https://github.com/rubocop/rubocop-ast/pull/112): Add `NodePattern` support for Regexp literals. ([@marcandre][]) ### Changes * [#22](https://github.com/rubocop/rubocop-ast/issues/22): **(Potentially breaking)** Most constants are now private, the rest are converted to Sets and meant to be private. ([@marcandre][]) ## 0.6.0 (2020-09-26) ### New features * [#124](https://github.com/rubocop/rubocop-ast/pull/124): Add `RegexpNode#options`. ([@owst][]) ## 0.5.1 (2020-09-25) ### Bug fixes * [#120](https://github.com/rubocop/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/rubocop-ast/pull/122): Add `Node#parent?` and `Node#root?`. ([@marcandre][]) ### Changes * [#121](https://github.com/rubocop/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/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/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/rubocop-ast/pull/92): Add `ProcessedSource#tokens_within`, `ProcessedSource#first_token_of` and `ProcessedSource#last_token_of`. ([@fatkodima][]) * [#88](https://github.com/rubocop/rubocop-ast/pull/88): Add `RescueNode`. Add `ResbodyNode#exceptions` and `ResbodyNode#branch_index`. ([@fatkodima][]) * [#89](https://github.com/rubocop/rubocop-ast/pull/89): Support right hand assignment for Ruby 2.8 (3.0) parser. ([@koic][]) * [#93](https://github.com/rubocop/rubocop-ast/pull/93): Add `Node#{left|right}_sibling{s}` ([@marcandre][]) * [#99](https://github.com/rubocop/rubocop-ast/pull/99): Add `ConstNode` and some helper methods. ([@marcandre][]) ### Changes * [#94](https://github.com/rubocop/rubocop-ast/pull/94): In Ruby 2.4, `Set#===` is harmonized with Ruby 2.5+ to call `include?`. ([@marcandre][]) * [#91](https://github.com/rubocop/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/rubocop-ast/pull/70): Add `NextNode` ([@marcandre][]) * [#85](https://github.com/rubocop/rubocop-ast/pull/85): Add `IntNode#value` and `FloatNode#value`. ([@fatkodima][]) * [#82](https://github.com/rubocop/rubocop-ast/pull/82): `NodePattern`: Allow comments ([@marcandre][]) * [#83](https://github.com/rubocop/rubocop-ast/pull/83): Add `ProcessedSource#comment_at_line` ([@marcandre][]) * [#83](https://github.com/rubocop/rubocop-ast/pull/83): Add `ProcessedSource#each_comment_in_lines` ([@marcandre][]) * [#84](https://github.com/rubocop/rubocop-ast/pull/84): Add `Source::Range#line_span` ([@marcandre][]) * [#87](https://github.com/rubocop/rubocop-ast/pull/87): Add `CaseNode#branches` ([@marcandre][]) ### Bug fixes * [#70](https://github.com/rubocop/rubocop-ast/pull/70): Fix arguments processing for `BreakNode` ([@marcandre][]) * [#70](https://github.com/rubocop/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/rubocop-ast/issue/44): **(Breaking)** Use `parser` flag `self.emit_forward_arg = true` by default. ([@marcandre][]) * [#86](https://github.com/rubocop/rubocop-ast/pull/86): `PairNode#delimiter` and `inverse_delimiter` now accept their argument as a named argument. ([@marcandre][]) * [#87](https://github.com/rubocop/rubocop-ast/pull/87): **(Potentially breaking)** Have `IfNode#branches` return a `nil` value if source has `else; end` ([@marcandre][]) * [#72](https://github.com/rubocop/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/rubocop-ast/pull/50): Support find pattern matching for Ruby 2.8 (3.0) parser. ([@koic][]) * [#55](https://github.com/rubocop/rubocop-ast/pull/55): Add `ProcessedSource#line_with_comment?`. ([@marcandre][]) * [#63](https://github.com/rubocop/rubocop-ast/pull/63): NodePattern now supports patterns as arguments to predicate and functions. ([@marcandre][]) * [#64](https://github.com/rubocop/rubocop-ast/pull/64): Add `Node#global_const?`. ([@marcandre][]) * [#28](https://github.com/rubocop/rubocop-ast/issues/28): Add `struct_constructor?`, `class_definition?` and `module_definition?` matchers. ([@tejasbubane][]) ### Bug fixes * [#55](https://github.com/rubocop/rubocop-ast/pull/55): Fix `ProcessedSource#commented?` for multi-line ranges. Renamed `contains_comment?` ([@marcandre][]) * [#69](https://github.com/rubocop/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/rubocop-ast/pull/36): Add `post_condition_loop?` and `loop_keyword?` for `Node`. ([@fatkodima][]) * [#38](https://github.com/rubocop/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/rubocop-ast/pull/37): Add `enumerable_method?` for `MethodIdentifierPredicates`. ([@fatkodima][]) * [#4](https://github.com/rubocop/rubocop-ast/issues/4): Add `interpolation?` for `RegexpNode`. ([@tejasbubane][]) * [#20](https://github.com/rubocop/rubocop-ast/pull/20): Add option predicates for `RegexpNode`. ([@owst][]) * [#11](https://github.com/rubocop/rubocop-ast/issues/11): Add `argument_type?` method to make it easy to recognize argument nodes. ([@tejasbubane][]) * [#31](https://github.com/rubocop/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/rubocop-ast/pull/41): Add `delimiters` and related predicates for `RegexpNode`. ([@owst][]) * [#46](https://github.com/rubocop/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/rubocop-ast/pull/48): Support `Parser::Ruby28` for Ruby 2.8 (3.0) parser (experimental). ([@koic][]) * [#35](https://github.com/rubocop/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/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/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/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 [@eugeneius]: https://github.com/eugeneius [@wcmonty]: https://github.com/wcmonty [@zverok]: https://github.com/zverok [@gsamokovarov]: https://github.com/gsamokovarov [@nobuyo]: https://github.com/nobuyo [@tdeo]: https://github.com/tdeo [@ydah]: https://github.com/ydahrubocop-ast-1.24.0/CONTRIBUTING.md000066400000000000000000000100301434172225000162570ustar00rootroot00000000000000# 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) by creating a file `changelog/{type}_{some_description}.md`. 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/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/rubocop-ast/pull/7542): **(Breaking)** Move `LineLength` cop from `Metrics` department to `Layout` department. ([@koic][]) ``` * Create one file `changelog/{type}_{some_description}.md`, where `type` is `new` (New feature), `fix` or `change`, and `some_description` is unique to avoid conflicts. * 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/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][])`. * The rake tasks `rake changelog:new|bug|change` will create a file with the body based off the last git commit. Modify it and add it to your commit; maintainers will merge it automatically later. * Alternatively, you may modify the CHANGELOG file directly, but this may result in conflicts later on. Also, if this is your first contribution to RuboCop project, you'll have to 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/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-1.24.0/Gemfile000066400000000000000000000022211434172225000153240ustar00rootroot00000000000000# frozen_string_literal: true ##### IMPORTANT: All dependencies that require 'rubocop' ##### MUST be in the `else` section below! ##### This is so we can run our specs completely independently from RuboCop source 'https://rubygems.org' gemspec gem 'bump', require: false gem 'oedipus_lex', '>= 2.6.0', require: false gem 'racc' gem 'rake', '~> 13.0' gem 'rspec', '~> 3.7' # 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' if ENV.fetch('RUBOCOP_VERSION', nil) == 'none' # Set this way on CI puts 'Running specs independently of RuboCop' else local_ast = File.expand_path('../rubocop', __dir__) if File.exist?(local_ast) gem 'rubocop', path: local_ast elsif ENV.fetch('RUBOCOP_VERSION', nil) == 'master' gem 'rubocop', git: 'https://github.com/rubocop/rubocop.git' else gem 'rubocop', '>= 1.0' end gem 'rubocop-performance' gem 'rubocop-rspec' end local_gemfile = File.expand_path('Gemfile.local', __dir__) eval_gemfile local_gemfile if File.exist?(local_gemfile) rubocop-ast-1.24.0/LICENSE.txt000066400000000000000000000020461434172225000156610ustar00rootroot00000000000000Copyright (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-1.24.0/README.md000066400000000000000000000043011434172225000153110ustar00rootroot00000000000000# RuboCop AST [![Gem Version](https://badge.fury.io/rb/rubocop-ast.svg)](https://badge.fury.io/rb/rubocop-ast) [![CI](https://github.com/rubocop/rubocop-ast/workflows/CI/badge.svg)](https://github.com/rubocop/rubocop-ast/actions?query=workflow%3ACI) [![Test Coverage](https://api.codeclimate.com/v1/badges/a29666e6373bc41bc0a9/test_coverage)](https://codeclimate.com/github/rubocop/rubocop-ast/test_coverage) [![Maintainability](https://api.codeclimate.com/v1/badges/a29666e6373bc41bc0a9/maintainability)](https://codeclimate.com/github/rubocop/rubocop-ast/maintainability) Contains the classes needed by [RuboCop](https://github.com/rubocop/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 the following which are set to `true`: * `emit_forward_arg` * `emit_match_pattern` 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-1.24.0/Rakefile000066400000000000000000000013351434172225000155030ustar00rootroot00000000000000# frozen_string_literal: true task build: :generate task release: 'changelog:check_clean' 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: :generate) 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: :generate do sh 'rubocop' end task default: %i[ spec internal_investigation ] rubocop-ast-1.24.0/codespell.txt000066400000000000000000000000051434172225000165420ustar00rootroot00000000000000upto rubocop-ast-1.24.0/docs/000077500000000000000000000000001434172225000147645ustar00rootroot00000000000000rubocop-ast-1.24.0/docs/antora.yml000066400000000000000000000001241434172225000167700ustar00rootroot00000000000000name: rubocop-ast title: RuboCop AST version: '1.24' nav: - modules/ROOT/nav.adoc rubocop-ast-1.24.0/docs/modules/000077500000000000000000000000001434172225000164345ustar00rootroot00000000000000rubocop-ast-1.24.0/docs/modules/ROOT/000077500000000000000000000000001434172225000172175ustar00rootroot00000000000000rubocop-ast-1.24.0/docs/modules/ROOT/nav.adoc000066400000000000000000000003021434172225000206260ustar00rootroot00000000000000* xref:index.adoc[Home] * xref:installation.adoc[Installation] * xref:node_types.adoc[Node Types] * xref:node_pattern.adoc[Node Pattern] * xref:node_pattern_compiler.adoc[Node Pattern Compiler] rubocop-ast-1.24.0/docs/modules/ROOT/pages/000077500000000000000000000000001434172225000203165ustar00rootroot00000000000000rubocop-ast-1.24.0/docs/modules/ROOT/pages/index.adoc000066400000000000000000000056351434172225000222660ustar00rootroot00000000000000= 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-1.24.0/docs/modules/ROOT/pages/installation.adoc000066400000000000000000000003131434172225000236440ustar00rootroot00000000000000= 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-1.24.0/docs/modules/ROOT/pages/node_pattern.adoc000066400000000000000000000345361434172225000236430ustar00rootroot00000000000000= 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" (union) 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 Branches of the union can contain more than one term: * `(array {int int | range})` - matches an array with two integers or a single range element If all the branches have a single term, you can omit the `|`, so `{int | float}` can be simplified to `{int float}`. When checking for symbols or string, you can use regexp literals for a similar effect: [source,sh] ---- (send _ /to_s|inspect/) # => matches calls to `to_s` or `inspect` ---- == `[]` 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 sequence; 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?) ---- You may call a method on a constant too. Let's say you define: [source,ruby] ---- module Util def self.palindrome?(str) str == str.reverse end end ---- You can refer to it like this: ---- (str #Util.palindrome?) ---- == 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-ast/RuboCop/AST/NodePattern/Macros#def_node_matcher-instance_method[def_node_matcher] and https://www.rubydoc.info/gems/rubocop-ast/RuboCop/AST/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_sensitive_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_sensitive_data?(node, /^pass(word)?$/i) # one can also pass lambdas... has_sensitive_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` or `%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 preceding 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/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/rubocop-ast/blob/master/spec/rubocop/ast/node_pattern_spec.rb[specs] are also very useful to comprehend each feature. rubocop-ast-1.24.0/docs/modules/ROOT/pages/node_pattern_compiler.adoc000066400000000000000000000231511434172225000255240ustar00rootroot00000000000000= Hacker's guide to the `NodePattern` compiler This documentation is aimed at anyone wanting to understand / modify the `NodePattern` compiler. It assumes some familiarity with the syntax of https://github.com/rubocop/rubocop-ast/blob/master/docs/modules/ROOT/pages/node_pattern.adoc[`NodePattern`], as well as the AST produced by the `parser` gem. == High level view The `NodePattern` compiler uses the same techniques as the `parser` gem: * a `Lexer` that breaks source into tokens * a `Parser` that uses tokens and a `Builder` to emit an AST * a `Compiler` that converts this AST into Ruby code Example: * Pattern: `+(send nil? {:puts :p} $...)+` * Tokens: `+'(', [:tNODE_TYPE, :send], [:tPREDICATE, :nil?], '{', ...+` * AST: `+s(:sequence, s(:node_type, :send), s(:predicate, :nil?), s(:union, ...+` * Ruby code: + [source,ruby] ---- node.is_a?(::RuboCop::AST::Node) && node.children.size >= 2 && node.send_type? && node.children[0].nil?() && (union2 = node.children[1]; ... ---- The different parts are described below == Vocabulary *"node pattern"*: something that can be matched against a single `AST::Node`. While `(int 42)` and `#is_fun?` both correspond to node patterns, `+...+` (without the parenthesis) is not a node pattern. *"sequence"*: a node pattern that describes the sequence of children of a node (and its type): `+(type first_child second_child ...)+` *"variadic"*: element of a sequence that can match a variable number of children. `+(send _ int* ...)+` has two variadic elements (`int*` and `+...+`). `(send _ :name)` contains no variadic element. Note that a sequence is itself never variadic. *"atom"*: element of a pattern that corresponds with a simple Ruby object. `(send nil? :puts (str 'hello'))` has two atoms: `:puts` and `'hello'`. == Lexer The `lexer.rb` defines `Lexer` and has the few definitions needed for the lexer to work. The bulk of the processing is in the inherited class that is generated by https://github.com/seattlerb/oedipus_lex[`oedipus_lex`] [discrete] ==== Rules https://github.com/seattlerb/oedipus_lex[`oedipus_lex`] generates the Ruby file `lexer.rex.rb` from the rules defined in `lexer.rex`. These rules map a Regexp to code that emits a token. `oedipus_lex` aims to be simple and the generated file is readable. It uses https://ruby-doc.org/stdlib-2.7.1/libdoc/strscan/rdoc/StringScanner.html[`StringScanner`] behind the scene. It selects the first rule that matches, contrary to many lexing tools that prioritize longest match. [discrete] ==== Tokens The `Lexer` emits tokens with types that are: * string for the syntactic symbols (e.g. `'('`, `'$'`, `+'...'+`) * symbols of the form `:tTOKEN_TYPE` for the rest (e.g. `:tPREDICATE`) Tokens are stored as `[type, value]`, or `[type, [value, location]]` if locations are emitted. [discrete] ==== Generation Use `rake generate:lexer` to generate the `lexer.rex.rb` from `lexer.rex` file. This is done automatically by `rake spec`. NOTE: the `lexer.rex.rb` is not under source control, but is included in the gem. == Parser Similarly to the `Lexer`, the `parser.rb` defines `Parser` and has the few definitions needed for the parser to work. The bulk of the processing is in the inherited class `parser.racc.rb` that is generated by https://ruby-doc.org/stdlib-2.7.0/libdoc/racc/parser/rdoc/Racc.html#module-Racc-label-Writing+A+Racc+Grammar+File[`racc`] from the rules in `parser.y`. [discrete] ==== Nodes The `Parser` emits `NodePattern::Node` which are similar to RuboCop's node. They both inherit from ``parser``'s `Parser::AST::Source::Node`, and share additional methods too. Like for RuboCop's nodes, some nodes have specicialized classes (e.g. `Sequence`) while other nodes use the base class directly (e.g. `s(:number, 42)`) [discrete] ==== Rules The rules follow closely the definitions above. In particular a distinction between `node_pattern_list`, which is a list of node patterns (each term can match a single node), while the more generic `variadic_pattern_list` is a list of elements, some of which could be variadic, others simple node patterns. [discrete] ==== Generation Similarly to the lexer, use `rake generate:parser` to generate the `parser.racc.rb` from `parser.y` file. This is done automatically by `rake spec`. NOTE: the `parser.racc.rb` is not under source control, but is included in the gem. == Compiler The compiler's core is the `Compiler` class. It holds the global state (e.g. references to named arguments). The goal of the compiler is to produce `matching_code`, Ruby code that can be run against an `AST::Node`, or any Ruby object for that matter. Packaging of that `matching_code` into code for a `lambda`, or method `def` is handled separately by the `MethodDefiner` module. The compilation itself is handled by three subcompilers: * `NodePatternSubcompiler` * `AtomSubcompiler` * `SequenceSubcompiler` === Visitors The subcompilers use the visitor pattern [https://en.wikipedia.org/wiki/Visitor_pattern] The methods starting with `visit_` are used to process the different types of nodes. For a node of type `:capture`, the method `visit_capture` will be called, or if none is defined then `visit_other_type` will be called. No argument is passed, as the visited node is accessible with the `node` attribute reader. === NodePatternSubcompiler Given any `NodePattern::Node`, it generates the Ruby code that can return `true` or `false` for the given node, or node type for sequence head. ==== `var` vs `access` The subcompiler can be called with the current node stored either in a variable (provided with the `var:` keyword argument) or via a Ruby expression (e.g. `access: 'current_node.children[2]'`). The subcompiler will not generate code that executes this `access` expression more than once or twice. If it might access the node more than that, `multiple_access` will store the result in a temporary variable (e.g. `union`). ==== Sequences Sequences are the most difficult elements to handle and are deferred to the `SequenceSubcompiler`. ==== Atoms Atoms are handled with `visit_other_type`, which defers to the `AtomSubcompiler` and converts that result to a node pattern by appending `=== cur_node` (or `=== cur_node.type` if in sequence head). This way, the two arguments in `(_ #func?(%1) %2)` would be compiled differently; `%1` would be compiled as `param1`, while `%2` gets compiled as `param2 === node.children[1]`. ==== Precedence The code generated has higher or equal precedence to `&&`, so as to make chaining convenient. === AtomSubcompiler This subcompiler produces Ruby code that gets evaluated to a Ruby object. E.g. `"42"`, `:a_symbol`, `param1`. A good way to think about it is when it has to be passed as arguments to a function call. For example: [source,ruby] ---- # Pattern '#func(42, %1)' compiles to func(node, 42, param1) ---- Note that any node pattern can be output by this subcompiler, but those that don't correspond to a Ruby literal will be output as a lambda so they can be combined. For example: [source,ruby] ---- # Pattern '#func(int)' compiles to func(node, ->(compare) { compare.is_a?(::RuboCop::AST::Node) && compare.int_type? }) ---- === SequenceSubcompiler The subcompiler compiles the sequences' terms in turn, keeping track of which children of the `AST::Node` are being matched. ==== Variadic terms The complexity comes from variadic elements, which have complex processing _and_ may make it impossible to know at compile time which children are matched by the subsequent terms. *Example* (no variadic terms) ---- (_type int _ str) ---- First child must match `int`, third child must match `str`. The subcompiler will use `children[0]` and `children[2]`. *Example* (one variadic terms) ---- (_type int _* str) ---- First child must match `int` and _last_ child must match `str`. The subcompiler will use `children[0]` and `children[-1]`. *Example* (multiple variadic terms) ---- (_type int+ sym str+) ---- The subcompiler can not use any integer and `children[]` to match `sym`. This must be tracked at runtime in a variable (`cur_index`). The subcompiler will use fixed indices before the first variadic element and after the last one. ==== Node pattern terms The node pattern terms are delegated to the `NodePatternSubcompiler`. In the pattern `(:sym :sym)`, both `:sym` will be compiled differently because the first `:sym` is in "sequence head": `:sym === node.type` and `:sym == node.children[0]` respectively. The subcompiler indicates if the pattern is in "sequence head" or not, so the `NodePatternSubcompiler` can produce the right code. Variadic elements may not (currently) cover the sequence head. As a convenience, `+(...)+` is understood as `+(_ ...)+`. Other types of nodes will raise an error (e.g. `()`; see `Node#in_sequence_head`) ==== Precedence Like the node pattern subcompiler, it generates code that has higher or equal precedence to `&&`, so as to make chaining convenient. == Variant: WithMeta These variants of the Parser / Builder / Lexer generate `location` information (exactly like the `parser` gem) for AST nodes as well as comments with their locations (like the `parser` gem). Since this information is not typically used when one only wants to define methods, it is not loaded by default. == Variant: Debug These variants of the Compiler / Subcompilers works by adding tracing code before and after each compilation of `NodePatternSubcompiler` and `SequenceSubcompiler`. A unique ID is assigned to each node and the tracing code flips a corresponding switch when the expression is about to be evaluated, and after (joined with `&&` so it only flips the switch if the node was a match). Atoms are not compiled differently as they are not really matchable (when not compiled as a node pattern) rubocop-ast-1.24.0/docs/modules/ROOT/pages/node_types.adoc000066400000000000000000000520361434172225000233250ustar00rootroot00000000000000= 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/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/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/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 |https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AndAsgnNode[AndAsgnNode] |arg|Required positional argument. Must come inside an `args`.|One child - a symbol, representing the argument name.|def foo(bar)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode] |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/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/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/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)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode] |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/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/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|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/CasgnNode[CasgnNode] |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/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/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|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AsgnNode[AsgnNode] |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/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/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/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/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/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/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/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, ...)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode] |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/rubocop-ast/RuboCop/AST/ForwardArgsNode[ForwardArgsNode] |forwarded-args|Forwarding arguments into a method call|None|foo(...)|N/A |forwarded-restarg|Forwarding positional arguments into a method call|None|foo(*)|N/A |forwarded-kwrestarg|Forwarding keyword 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|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AsgnNode[AsgnNode] |hash|Hash literal.|`pair` s and/or `kwsplat` s.|{ foo: 'bar' }|https://rubydoc.info/github/rubocop/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/rubocop-ast/RuboCop/AST/IfNode[IfNode] |int|Integer literal|1, the integer value|-123|https://rubydoc.info/github/rubocop/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|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AsgnNode[AsgnNode] |irange|Inclusive range literal.|Two children, the start and end nodes (including `nil` for beginless/endless)|1..2|https://rubydoc.info/github/rubocop/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:)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode] |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)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode] |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/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)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode] |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|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/AsgnNode[AsgnNode] |masgn|Multiple assignment.|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. Used inside a `masgn` and block argument destructuring.|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/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/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|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/OpAsgnNode[OpAsgnNode] |optarg|Optional positional argument. Must come inside an `args`.|One child - a symbol, representing the argument name.|def foo(bar=1)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode] |or|Or operator|Two children are both expression nodes representing the operands.|a or b|https://rubydoc.info/github/rubocop/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|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/OrAsgnNode[OrAsgnNode] |pair|One entry in a hash. |Two children, the key and value nodes.|1 => 2|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/PairNode[PairNode] |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/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/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)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode] |return|Return statement|Zero or one child, an expression node for the value to return.|return|https://rubydoc.info/github/rubocop/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/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/rubocop-ast/RuboCop/AST/SendNode[SendNode] |shadowarg|Shadow argument, aka block-local variable. Must come inside an `args`.|One child - a symbol, representing the argument name.|foo { \|a; b\| b }|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/ArgNode[ArgNode] |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/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/rubocop-ast/RuboCop/AST/SuperNode[SuperNode] |sym|Non-interpolated symbol|One child, the Symbol content.|`:foo`|https://rubydoc.info/github/rubocop/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/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/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/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/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/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/rubocop-ast/RuboCop/AST/StrNode[StrNode] |yield|Yield to a block.|Children are expression nodes representing arguments.|yield(foo)|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/YieldNode[YieldNode] |zsuper|Super method call with no arguments or brackets.|None|super|https://rubydoc.info/github/rubocop/rubocop-ast/RuboCop/AST/SuperNode[SuperNode] |============================================= rubocop-ast-1.24.0/lib/000077500000000000000000000000001434172225000146025ustar00rootroot00000000000000rubocop-ast-1.24.0/lib/rubocop-ast.rb000066400000000000000000000000761434172225000173700ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'rubocop/ast' rubocop-ast-1.24.0/lib/rubocop/000077500000000000000000000000001434172225000162535ustar00rootroot00000000000000rubocop-ast-1.24.0/lib/rubocop/ast.rb000066400000000000000000000075251434172225000174000ustar00rootroot00000000000000# frozen_string_literal: true require 'parser' require 'forwardable' require 'set' require_relative 'ast/ext/range' require_relative 'ast/ext/range_min_max' require_relative 'ast/node_pattern/method_definer' require_relative 'ast/node_pattern' require_relative 'ast/node/mixin/descendence' require_relative 'ast/node_pattern/builder' require_relative 'ast/node_pattern/comment' require_relative 'ast/node_pattern/compiler' require_relative 'ast/node_pattern/compiler/subcompiler' require_relative 'ast/node_pattern/compiler/atom_subcompiler' require_relative 'ast/node_pattern/compiler/binding' require_relative 'ast/node_pattern/compiler/node_pattern_subcompiler' require_relative 'ast/node_pattern/compiler/sequence_subcompiler' require_relative 'ast/node_pattern/lexer' require_relative 'ast/node_pattern/node' require_relative 'ast/node_pattern/parser' require_relative 'ast/node_pattern/sets' 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/arg_node' require_relative 'ast/node/args_node' require_relative 'ast/node/array_node' require_relative 'ast/node/asgn_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/casgn_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/in_pattern_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/op_asgn_node' require_relative 'ast/node/and_asgn_node' require_relative 'ast/node/or_asgn_node' require_relative 'ast/node/or_node' require_relative 'ast/node/pair_node' require_relative 'ast/node/procarg0_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/dstr_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::NodePattern::Parser.autoload :WithMeta, "#{__dir__}/ast/node_pattern/with_meta" ::RuboCop::AST::NodePattern::Compiler.autoload :Debug, "#{__dir__}/ast/node_pattern/compiler/debug" rubocop-ast-1.24.0/lib/rubocop/ast/000077500000000000000000000000001434172225000170425ustar00rootroot00000000000000rubocop-ast-1.24.0/lib/rubocop/ast/builder.rb000066400000000000000000000067341434172225000210270ustar00rootroot00000000000000# 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 if respond_to?(:emit_forward_arg=) self.emit_match_pattern = true if respond_to?(:emit_match_pattern=) # @api private NODE_MAP = { and: AndNode, and_asgn: AndAsgnNode, alias: AliasNode, arg: ArgNode, blockarg: ArgNode, forward_arg: ArgNode, kwarg: ArgNode, kwoptarg: ArgNode, kwrestarg: ArgNode, optarg: ArgNode, restarg: ArgNode, shadowarg: ArgNode, args: ArgsNode, array: ArrayNode, lvasgn: AsgnNode, ivasgn: AsgnNode, cvasgn: AsgnNode, gvasgn: AsgnNode, block: BlockNode, numblock: BlockNode, break: BreakNode, case_match: CaseMatchNode, casgn: CasgnNode, case: CaseNode, class: ClassNode, const: ConstNode, def: DefNode, defined?: DefinedNode, defs: DefNode, dstr: DstrNode, ensure: EnsureNode, for: ForNode, forward_args: ForwardArgsNode, float: FloatNode, hash: HashNode, if: IfNode, in_pattern: InPatternNode, int: IntNode, index: IndexNode, indexasgn: IndexasgnNode, irange: RangeNode, erange: RangeNode, kwargs: HashNode, kwsplat: KeywordSplatNode, lambda: LambdaNode, module: ModuleNode, next: NextNode, op_asgn: OpAsgnNode, or_asgn: OrAsgnNode, or: OrNode, pair: PairNode, procarg0: Procarg0Node, regexp: RegexpNode, rescue: RescueNode, resbody: ResbodyNode, return: ReturnNode, csend: SendNode, send: SendNode, str: 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-1.24.0/lib/rubocop/ast/ext/000077500000000000000000000000001434172225000176425ustar00rootroot00000000000000rubocop-ast-1.24.0/lib/rubocop/ast/ext/range.rb000066400000000000000000000014201434172225000212600ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/ext/range_min_max.rb000066400000000000000000000006051434172225000227740ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST module Ext # Refinement to circumvent broken `Range#minmax` for infinity ranges in 2.6- module RangeMinMax if ::Range.instance_method(:minmax).owner != ::Range refine ::Range do def minmax [min, max] end end end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node.rb000066400000000000000000000503671434172225000203270ustar00rootroot00000000000000# 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 include RuboCop::AST::Descendence # @api private # <=> isn't included here, because it doesn't return a boolean. COMPARISON_OPERATORS = %i[== === != <= >= > <].to_set.freeze # @api private TRUTHY_LITERALS = %i[str dstr xstr int float sym dsym array hash regexp true irange erange complex rational regopt].to_set.freeze # @api private FALSEY_LITERALS = %i[false nil].to_set.freeze # @api private LITERALS = (TRUTHY_LITERALS + FALSEY_LITERALS).freeze # @api private COMPOSITE_LITERALS = %i[dstr xstr dsym array hash irange erange regexp].to_set.freeze # @api private BASIC_LITERALS = (LITERALS - COMPOSITE_LITERALS).freeze # @api private MUTABLE_LITERALS = %i[str dstr xstr array hash regexp irange erange].to_set.freeze # @api private IMMUTABLE_LITERALS = (LITERALS - MUTABLE_LITERALS).freeze # @api private EQUALS_ASSIGNMENTS = %i[lvasgn ivasgn cvasgn gvasgn casgn masgn].to_set.freeze # @api private SHORTHAND_ASSIGNMENTS = %i[op_asgn or_asgn and_asgn].to_set.freeze # @api private ASSIGNMENTS = (EQUALS_ASSIGNMENTS + SHORTHAND_ASSIGNMENTS).freeze # @api private BASIC_CONDITIONALS = %i[if while until].to_set.freeze # @api private CONDITIONALS = (BASIC_CONDITIONALS + %i[case case_match]).freeze # @api private POST_CONDITION_LOOP_TYPES = %i[while_post until_post].to_set.freeze # @api private LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + %i[while until for]).freeze # @api private VARIABLES = %i[ivar gvar cvar lvar].to_set.freeze # @api private REFERENCES = %i[nth_ref back_ref].to_set.freeze # @api private 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].to_set.freeze # @api private OPERATOR_KEYWORDS = %i[and or].to_set.freeze # @api private SPECIAL_KEYWORDS = %w[__FILE__ __LINE__ __ENCODING__].to_set.freeze # @api private ARGUMENT_TYPES = %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg forward_arg shadowarg].to_set.freeze LITERAL_RECURSIVE_METHODS = (COMPARISON_OPERATORS + %i[* ! <=>]).freeze LITERAL_RECURSIVE_TYPES = (OPERATOR_KEYWORDS + COMPOSITE_LITERALS + %i[begin pair]).freeze private_constant :LITERAL_RECURSIVE_METHODS, :LITERAL_RECURSIVE_TYPES # @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..].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 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 # 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 # @!method receiver(node = self) def_node_matcher :receiver, <<~PATTERN {(send $_ ...) ({block numblock} (call $_ ...) ...)} PATTERN # @!method str_content(node = self) 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 # @!method defined_module0(node = self) 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) do |full_name| return nil unless full_name full_name end 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. # @!method assignment_or_similar?(node = self) 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 LITERAL_RECURSIVE_METHODS.include?(method_name) && receiver.send(recursive_kind) && arguments.all?(&recursive_kind) when LITERAL_RECURSIVE_TYPES 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? || rational_type? || complex_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 # @!method match_guard_clause?(node = self) def_node_matcher :match_guard_clause?, <<~PATTERN [${(send nil? {:raise :fail} ...) return break next} single_line?] PATTERN # @!method proc?(node = self) def_node_matcher :proc?, <<~PATTERN {(block (send nil? :proc) ...) (block (send #global_const?(:Proc) :new) ...) (send #global_const?(:Proc) :new)} PATTERN # @!method lambda?(node = self) def_node_matcher :lambda?, '({block numblock} (send nil? :lambda) ...)' # @!method lambda_or_proc?(node = self) def_node_matcher :lambda_or_proc?, '{lambda? proc?}' # @!method global_const?(node = self, name) def_node_matcher :global_const?, '(const {nil? cbase} %1)' # @!method class_constructor?(node = self) def_node_matcher :class_constructor?, <<~PATTERN { (send #global_const?({:Class :Module :Struct}) :new ...) (block (send #global_const?({:Class :Module :Struct}) :new ...) ...)} PATTERN # @deprecated Use `:class_constructor?` # @!method struct_constructor?(node = self) def_node_matcher :struct_constructor?, <<~PATTERN ({block numblock} (send #global_const?(:Struct) :new ...) _ $_) PATTERN # @!method class_definition?(node = self) def_node_matcher :class_definition?, <<~PATTERN {(class _ _ $_) (sclass _ $_) ({block numblock} (send #global_const?({:Struct :Class}) :new ...) _ $_)} PATTERN # @!method module_definition?(node = self) def_node_matcher :module_definition?, <<~PATTERN {(module _ $_) ({block numblock} (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 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 # @!method new_class_or_module_block?(node = self) def_node_matcher :new_class_or_module_block?, <<~PATTERN ^(casgn _ _ (block (send (const _ {:Class :Module}) :new) ...)) PATTERN end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/000077500000000000000000000000001434172225000177675ustar00rootroot00000000000000rubocop-ast-1.24.0/lib/rubocop/ast/node/alias_node.rb000066400000000000000000000012231434172225000224100ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/and_asgn_node.rb000066400000000000000000000007351434172225000231000ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `op_asgn` nodes. # This will be used in place of a plain node when the builder constructs # the AST, making its methods available to all assignment nodes within RuboCop. class AndAsgnNode < OpAsgnNode # The operator being used for assignment as a symbol. # # @return [Symbol] the assignment operator def operator :'&&' end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/and_node.rb000066400000000000000000000016321434172225000220650ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/arg_node.rb000066400000000000000000000017131434172225000220740ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `arg`, `optarg`, `restarg`, `kwarg`, `kwoptarg`, # `kwrestarg`, `blockarg`, `shadowarg` and `forward_arg` nodes. # This will be used in place of a plain node when the builder constructs # the AST, making its methods available to all `arg` nodes within RuboCop. class ArgNode < Node # Returns the name of an argument. # # @return [Symbol, nil] the name of the argument def name node_parts[0] end # Returns the default value of the argument, if any. # # @return [Node, nil] the default value of the argument def default_value return unless default? node_parts[1] end # Checks whether the argument has a default value # # @return [Boolean] whether the argument has a default value def default? optarg_type? || kwoptarg_type? end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/args_node.rb000066400000000000000000000021611434172225000222550ustar00rootroot00000000000000# 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 # Yield each argument from the collection. # Arguments can be inside `mlhs` nodes in the case of destructuring, so this # flattens the collection to just `arg`, `optarg`, `restarg`, `kwarg`, # `kwoptarg`, `kwrestarg`, `blockarg`, `forward_arg` and `shadowarg`. # # @return [Array] array of argument nodes. def argument_list each_descendant(*ARGUMENT_TYPES).to_a.freeze end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/array_node.rb000066400000000000000000000036141434172225000224430ustar00rootroot00000000000000# 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 private_constant :PERCENT_LITERAL_TYPES # 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 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-1.24.0/lib/rubocop/ast/node/asgn_node.rb000066400000000000000000000012651434172225000222550ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `lvasgn`, `ivasgn`, `cvasgn`, and `gvasgn` nodes. # This will be used in place of a plain node when the builder constructs # the AST, making its methods available to all assignment nodes within RuboCop. class AsgnNode < Node # The name of the variable being assigned as a symbol. # # @return [Symbol] the name of the variable being assigned def name node_parts[0] end # The expression being assigned to the variable. # # @return [Node] the expression being assigned. def expression node_parts[1] end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/block_node.rb000066400000000000000000000104651434172225000224210ustar00rootroot00000000000000# 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 private_constant :VOID_CONTEXT_METHODS # 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. # Note that if the block has destructured arguments, `arguments` will # return a `mlhs` node, whereas `argument_list` will return only # actual argument nodes. # # @return [Array] def arguments if numblock_type? [].freeze # Numbered parameters have no block arguments. else node_parts[1] end end # Returns a collection of all descendants of this node that are # argument type nodes. See `ArgsNode#argument_list` for details. # # @return [Array] def argument_list if numblock_type? numbered_arguments else arguments.argument_list 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 private # Numbered arguments of this `numblock`. def numbered_arguments return [].freeze unless numblock_type? max_param = children[1] 1.upto(max_param).map do |i| ArgNode.new(:arg, [:"_#{i}"]) end.freeze end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/break_node.rb000066400000000000000000000005421434172225000224060ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/case_match_node.rb000066400000000000000000000037531434172225000234200ustar00rootroot00000000000000# 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 in_pattern_branches.each(&block) self end # Returns an array of all the `in` pattern branches in the `case` statement. # # @return [Array] an array of `in_pattern` nodes def in_pattern_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 `in` branches # and the `else` (if any). Note that these bodies could be nil. def branches bodies = in_pattern_branches.map(&:body) if else? # `empty-else` node sets nil because it has no body. else_branch.empty_else_type? ? bodies.push(nil) : bodies.push(else_branch) end bodies end # Returns the else branch of the `case` statement, if any. # # @return [Node] the else branch node of the `case` statement # @return [EmptyElse] the empty 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-1.24.0/lib/rubocop/ast/node/case_node.rb000066400000000000000000000033051434172225000222350ustar00rootroot00000000000000# 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 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-1.24.0/lib/rubocop/ast/node/casgn_node.rb000066400000000000000000000015361434172225000224210ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `casgn` nodes. # This will be used in place of a plain node when the builder constructs # the AST, making its methods available to all assignment nodes within RuboCop. class CasgnNode < Node # The namespace of the constant being assigned. # # @return [Node, nil] the node associated with the scope (e.g. cbase, const, ...) def namespace node_parts[0] end # The name of the variable being assigned as a symbol. # # @return [Symbol] the name of the variable being assigned def name node_parts[1] end # The expression being assigned to the variable. # # @return [Node] the expression being assigned. def expression node_parts[2] end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/class_node.rb000066400000000000000000000014111434172225000224230ustar00rootroot00000000000000# 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 identifier for this `class` node. # # @return [Node] the identifier 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-1.24.0/lib/rubocop/ast/node/const_node.rb000066400000000000000000000031451434172225000224520ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `const` nodes. class ConstNode < Node # @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 # @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 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-1.24.0/lib/rubocop/ast/node/def_node.rb000066400000000000000000000040571434172225000220650ustar00rootroot00000000000000# 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 # @return [Boolean] if the definition is without an `end` or not. def endless? !loc.end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/defined_node.rb000066400000000000000000000007251434172225000227230ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/dstr_node.rb000066400000000000000000000006771434172225000223070ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `dstr` nodes. This will be used # in place of a plain node when the builder constructs the AST, making # its methods available to all `dstr` nodes within RuboCop. class DstrNode < StrNode def value child_nodes.map do |child| child.respond_to?(:value) ? child.value : child.source end.join end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/ensure_node.rb000066400000000000000000000007211434172225000226220ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/float_node.rb000066400000000000000000000005511434172225000224270ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/for_node.rb000066400000000000000000000026051434172225000221120ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/forward_args_node.rb000066400000000000000000000016651434172225000240110ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/hash_node.rb000066400000000000000000000070611434172225000222500ustar00rootroot00000000000000# 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 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 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-1.24.0/lib/rubocop/ast/node/if_node.rb000066400000000000000000000122651434172225000217250ustar00rootroot00000000000000# 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 if ternary? [if_branch, else_branch] elsif !else? [if_branch] else branches = [if_branch] other_branches = if elsif_conditional? else_branch.branches else [else_branch] end branches.concat(other_branches) end end # @deprecated Use `branches.each` def each_branch(&block) return branches.to_enum(__method__) unless block branches.each(&block) end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/in_pattern_node.rb000066400000000000000000000020011434172225000234550ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `in` nodes. This will be used in place of a plain # node when the builder constructs the AST, making its methods available # to all `in` nodes within RuboCop. class InPatternNode < Node # Returns a node of the pattern in the `in` branch. # # @return [Node] a pattern node def pattern node_parts.first end # Returns the index of the `in` branch within the `case` statement. # # @return [Integer] the index of the `in` branch def branch_index parent.in_pattern_branches.index(self) end # Checks whether the `in` node has a `then` keyword. # # @return [Boolean] whether the `in` node has a `then` keyword def then? loc.begin&.is?('then') end # Returns the body of the `in` node. # # @return [Node, nil] the body of the `in` node def body node_parts[-1] end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/index_node.rb000066400000000000000000000021401434172225000224250ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/indexasgn_node.rb000066400000000000000000000022411434172225000233000ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/int_node.rb000066400000000000000000000005431434172225000221150ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/keyword_splat_node.rb000066400000000000000000000023171434172225000242130ustar00rootroot00000000000000# 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 = '**' private_constant :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-1.24.0/lib/rubocop/ast/node/lambda_node.rb000066400000000000000000000025321434172225000225430ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/mixin/000077500000000000000000000000001434172225000211135ustar00rootroot00000000000000rubocop-ast-1.24.0/lib/rubocop/ast/node/mixin/basic_literal_node.rb000066400000000000000000000005341434172225000252440ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/mixin/binary_operator_node.rb000066400000000000000000000023011434172225000256400ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/mixin/collection_node.rb000066400000000000000000000005701434172225000246020ustar00rootroot00000000000000# 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 private_constant :ARRAY_METHODS def_delegators :to_a, *ARRAY_METHODS end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/mixin/conditional_node.rb000066400000000000000000000026471434172225000247610ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/mixin/descendence.rb000066400000000000000000000071421434172225000237040ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for primitive literal nodes: `sym`, `str`, # `int`, `float`, ... module Descendence # 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 any of the types. # @param [Symbol] type 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?(::AST::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 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 yield self if types.empty? || types.include?(type) visit_descendants(types, &block) self 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 end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/mixin/hash_element_node.rb000066400000000000000000000071501434172225000251040ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/mixin/method_dispatch_node.rb000066400000000000000000000222511434172225000256060ustar00rootroot00000000000000# 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 private_constant :ARITHMETIC_OPERATORS SPECIAL_MODIFIERS = %w[private protected].freeze private_constant :SPECIAL_MODIFIERS # 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` or `numblock` node associated with this method dispatch, if any. # # @return [BlockNode, nil] the `block` or `numblock` 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? || parent&.numblock_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` or `defs` modifiers. # # @example # # private def foo; end # # @return whether the `def|defs` node is a modifier or not. # See also `def_modifier` that returns the node or `nil` def def_modifier?(node = self) !!def_modifier(node) end # Checks if this node is part of a chain of `def` or `defs` modifiers. # # @example # # private def foo; end # # @return [Node | nil] returns the `def|defs` node this is a modifier for, # or `nil` if it isn't a def modifier def def_modifier(node = self) arg = node.children[2] return unless node.send_type? && node.receiver.nil? && arg.is_a?(::AST::Node) return arg if arg.def_type? || arg.defs_type? def_modifier(arg) 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 [Boolean] 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 # @!method in_macro_scope?(node = self) 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 numblock (if _condition <%0 _>) # note: we're excluding the condition of `if` nodes } #in_macro_scope? # that is itself in a macro scope ] } } PATTERN # @!method adjacent_def_modifier?(node = self) def_node_matcher :adjacent_def_modifier?, <<~PATTERN (send nil? _ ({def defs} ...)) PATTERN # @!method bare_access_modifier_declaration?(node = self) def_node_matcher :bare_access_modifier_declaration?, <<~PATTERN (send nil? {:public :protected :private :module_function}) PATTERN # @!method non_bare_access_modifier_declaration?(node = self) def_node_matcher :non_bare_access_modifier_declaration?, <<~PATTERN (send nil? {:public :protected :private :module_function} _) PATTERN end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb000066400000000000000000000203001434172225000273200ustar00rootroot00000000000000# 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 private_constant :ENUMERATOR_METHODS ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).to_set.freeze private_constant :ENUMERABLE_METHODS # http://phrogz.net/programmingruby/language.html#table_18.4 OPERATOR_METHODS = %i[| ^ & <=> == === =~ > >= < <= << >> + - * / % ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].to_set.freeze private_constant :OPERATOR_METHODS NONMUTATING_BINARY_OPERATOR_METHODS = %i[* / % + - == === != < > <= >= <=>].to_set.freeze private_constant :NONMUTATING_BINARY_OPERATOR_METHODS NONMUTATING_UNARY_OPERATOR_METHODS = %i[+@ -@ ~ !].to_set.freeze private_constant :NONMUTATING_UNARY_OPERATOR_METHODS NONMUTATING_OPERATOR_METHODS = (NONMUTATING_BINARY_OPERATOR_METHODS + NONMUTATING_UNARY_OPERATOR_METHODS).freeze private_constant :NONMUTATING_OPERATOR_METHODS 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 private_constant :NONMUTATING_ARRAY_METHODS 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 private_constant :NONMUTATING_HASH_METHODS 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 private_constant :NONMUTATING_STRING_METHODS # 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-1.24.0/lib/rubocop/ast/node/mixin/modifier_node.rb000066400000000000000000000006561434172225000242520ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/mixin/numeric_node.rb000066400000000000000000000007271434172225000241150ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for primitive numeric nodes: `int`, `float`, ... module NumericNode SIGN_REGEX = /\A[+-]/.freeze private_constant :SIGN_REGEX # 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-1.24.0/lib/rubocop/ast/node/mixin/parameterized_node.rb000066400000000000000000000072101434172225000253010ustar00rootroot00000000000000# 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..].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-1.24.0/lib/rubocop/ast/node/mixin/predicate_operator_node.rb000066400000000000000000000017741434172225000263310ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # Common functionality for nodes that are predicates: # `or`, `and` ... module PredicateOperatorNode LOGICAL_AND = '&&' private_constant :LOGICAL_AND SEMANTIC_AND = 'and' private_constant :SEMANTIC_AND LOGICAL_OR = '||' private_constant :LOGICAL_OR SEMANTIC_OR = 'or' private_constant :SEMANTIC_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-1.24.0/lib/rubocop/ast/node/module_node.rb000066400000000000000000000011461434172225000226100ustar00rootroot00000000000000# 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 identifier for this `module` node. # # @return [Node] the identifier 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-1.24.0/lib/rubocop/ast/node/next_node.rb000066400000000000000000000005371434172225000223040ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/op_asgn_node.rb000066400000000000000000000016621434172225000227540ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `op_asgn` nodes. # This will be used in place of a plain node when the builder constructs # the AST, making its methods available to all assignment nodes within RuboCop. class OpAsgnNode < Node # @return [AsgnNode] the assignment node def assignment_node node_parts[0] end # The name of the variable being assigned as a symbol. # # @return [Symbol] the name of the variable being assigned def name assignment_node.name end # The operator being used for assignment as a symbol. # # @return [Symbol] the assignment operator def operator node_parts[1] end # The expression being assigned to the variable. # # @return [Node] the expression being assigned. def expression node_parts.last end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/or_asgn_node.rb000066400000000000000000000007341434172225000227550ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `op_asgn` nodes. # This will be used in place of a plain node when the builder constructs # the AST, making its methods available to all assignment nodes within RuboCop. class OrAsgnNode < OpAsgnNode # The operator being used for assignment as a symbol. # # @return [Symbol] the assignment operator def operator :'||' end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/or_node.rb000066400000000000000000000016161434172225000217450ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/pair_node.rb000066400000000000000000000044751434172225000222660ustar00rootroot00000000000000# 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 = '=>' private_constant :HASH_ROCKET SPACED_HASH_ROCKET = ' => ' private_constant :SPACED_HASH_ROCKET COLON = ':' private_constant :COLON SPACED_COLON = ': ' private_constant :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 # Checks whether the `pair` uses hash value omission. # # @return [Boolean] whether this `pair` uses hash value omission def value_omission? source.end_with?(':') end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/procarg0_node.rb000066400000000000000000000007231434172225000230400ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST # A node extension for `procarg0` nodes. # This will be used in place of a plain node when the builder constructs # the AST, making its methods available to all `arg` nodes within RuboCop. class Procarg0Node < ArgNode # Returns the name of an argument. # # @return [Symbol, nil] the name of the argument def name node_parts[0].name end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/range_node.rb000066400000000000000000000006531434172225000224210ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/regexp_node.rb000066400000000000000000000051341434172225000226160ustar00rootroot00000000000000# 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, u: Regexp::FIXEDENCODING, o: 0 }.freeze private_constant :OPTIONS # @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 # @return [Bool] if regexp uses the fixed-encoding regopt def fixed_encoding? regopt_include?(:u) end private def regopt_include?(option) regopt.children.include?(option) end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/resbody_node.rb000066400000000000000000000024121434172225000227670ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/rescue_node.rb000066400000000000000000000030761434172225000226150ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/return_node.rb000066400000000000000000000005451434172225000226440ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/self_class_node.rb000066400000000000000000000011701434172225000234360ustar00rootroot00000000000000# 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 identifier for this `sclass` node. (Always `self`.) # # @return [Node] the identifier 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-1.24.0/lib/rubocop/ast/node/send_node.rb000066400000000000000000000013231434172225000222510ustar00rootroot00000000000000# 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 # @!method attribute_accessor?(node = self) 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-1.24.0/lib/rubocop/ast/node/str_node.rb000066400000000000000000000010261434172225000221300ustar00rootroot00000000000000# 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 character_literal? loc.respond_to?(:begin) && loc.begin && loc.begin.is?('?') end def heredoc? loc.is_a?(Parser::Source::Map::Heredoc) end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node/super_node.rb000066400000000000000000000012611434172225000224570ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/symbol_node.rb000066400000000000000000000005151434172225000226270ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/until_node.rb000066400000000000000000000017401434172225000224560ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/when_node.rb000066400000000000000000000023521434172225000222640ustar00rootroot00000000000000# 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 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-1.24.0/lib/rubocop/ast/node/while_node.rb000066400000000000000000000017401434172225000224330ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node/yield_node.rb000066400000000000000000000012241434172225000224260ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/node_pattern.rb000066400000000000000000000076621434172225000220640ustar00rootroot00000000000000# frozen_string_literal: true require 'delegate' module RuboCop module AST # This class performs a pattern-matching operation on an AST node. # # Detailed syntax: /docs/modules/ROOT/pages/node_pattern.adoc # # 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`. # class NodePattern # 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) NodePattern.new(pattern_str).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) NodePattern.new(pattern_str).def_node_search(self, method_name, **keyword_defaults) end end extend Forwardable include MethodDefiner Invalid = Class.new(StandardError) VAR = 'node' attr_reader :pattern, :ast, :match_code def_delegators :@compiler, :captures, :named_parameters, :positional_parameters def initialize(str, compiler: Compiler.new) @pattern = str @ast = compiler.parser.parse(str) @compiler = compiler @match_code = @compiler.compile_as_node_pattern(@ast, var: VAR) @cache = {} end def match(*args, **rest, &block) @cache[:lambda] ||= as_lambda @cache[:lambda].call(*args, block: block, **rest) end def ==(other) other.is_a?(NodePattern) && other.ast == ast end alias eql? == def to_s "#<#{self.class} #{pattern}>" end def marshal_load(pattern) # :nodoc: initialize pattern end def marshal_dump # :nodoc: pattern end def as_json(_options = nil) # :nodoc: pattern end def encode_with(coder) # :nodoc: coder['pattern'] = pattern end def init_with(coder) # :nodoc: initialize(coder['pattern']) end # Yields its argument and any descendants, depth-first. # def self.descend(element, &block) return to_enum(__method__, element) unless block yield element if element.is_a?(::RuboCop::AST::Node) element.children.each do |child| descend(child, &block) end end nil end def freeze @match_code.freeze @compiler.freeze super end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/000077500000000000000000000000001434172225000215245ustar00rootroot00000000000000rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/builder.rb000066400000000000000000000037101434172225000235000ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern # Responsible to build the AST nodes for `NodePattern` # # Doc on how this fits in the compiling process: # /docs/modules/ROOT/pages/node_pattern.adoc class Builder def emit_capture(capture_token, node) return node if capture_token.nil? emit_unary_op(:capture, capture_token, node) end def emit_atom(type, value) n(type, [value]) end def emit_unary_op(type, _operator = nil, *children) n(type, children) end def emit_list(type, _begin, children, _end) n(type, children) end def emit_call(type, selector, args = nil) _begin_t, arg_nodes, _end_t = args n(type, [selector, *arg_nodes]) end def emit_union(begin_t, pattern_lists, end_t) children = union_children(pattern_lists) type = optimizable_as_set?(children) ? :set : :union emit_list(type, begin_t, children, end_t) end def emit_subsequence(node_list) return node_list.first if node_list.size == 1 # Don't put a single child in a subsequence emit_list(:subsequence, nil, node_list, nil) end private def optimizable_as_set?(children) children.all?(&:matches_within_set?) end def n(type, *args) Node::MAP[type].new(type, *args) end def union_children(pattern_lists) if pattern_lists.size == 1 # {a b c} => [[a, b, c]] => [a, b, c] children = pattern_lists.first raise NodePattern::Invalid, 'A union can not be empty' if children.empty? children else # { a b | c } => [[a, b], [c]] => [s(:subsequence, a, b), c] pattern_lists.map do |list| emit_subsequence(list) end end end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/comment.rb000066400000000000000000000020401434172225000235070ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern # A NodePattern comment, simplified version of ::Parser::Source::Comment class Comment attr_reader :location alias loc location ## # @param [Parser::Source::Range] range # def initialize(range) @location = ::Parser::Source::Map.new(range) freeze end # @return [String] def text loc.expression.source.freeze end ## # Compares comments. Two comments are equal if they # correspond to the same source range. # # @param [Object] other # @return [Boolean] # def ==(other) other.is_a?(Comment) && @location == other.location end ## # @return [String] a human-readable representation of this comment # def inspect "#" end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/compiler.rb000066400000000000000000000055751434172225000236770ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern # The top-level compiler holding the global state # Defers work to its subcompilers # # Doc on how this fits in the compiling process: # /docs/modules/ROOT/pages/node_pattern.adoc class Compiler extend Forwardable attr_reader :captures, :named_parameters, :positional_parameters, :binding def initialize @temp_depth = 0 # avoid name clashes between temp variables @captures = 0 # number of captures seen @positional_parameters = 0 # highest % (param) number seen @named_parameters = Set[] # keyword parameters @binding = Binding.new # bound variables @atom_subcompiler = self.class::AtomSubcompiler.new(self) end def_delegators :binding, :bind def positional_parameter(number) @positional_parameters = number if number > @positional_parameters "param#{number}" end def named_parameter(name) @named_parameters << name name end # Enumerates `enum` while keeping track of state across # union branches (captures and unification). def each_union(enum, &block) enforce_same_captures(binding.union_bind(enum), &block) end def compile_as_atom(node) @atom_subcompiler.compile(node) end def compile_as_node_pattern(node, **options) self.class::NodePatternSubcompiler.new(self, **options).compile(node) end def compile_sequence(sequence, var:) self.class::SequenceSubcompiler.new(self, sequence: sequence, var: var).compile_sequence end def parser @parser ||= Parser.new end # Utilities def with_temp_variables(*names, &block) @temp_depth += 1 suffix = @temp_depth if @temp_depth > 1 names = block.parameters.map(&:last) if names.empty? names.map! { |name| "#{name}#{suffix}" } yield(*names) ensure @temp_depth -= 1 end def next_capture "captures[#{new_capture}]" end def freeze @named_parameters.freeze super end private def enforce_same_captures(enum) return to_enum __method__, enum unless block_given? captures_before = captures_after = nil enum.each do |node| captures_before ||= @captures @captures = captures_before yield node captures_after ||= @captures if captures_after != @captures raise Invalid, 'each branch must have same number of captures' end end end def new_capture @captures ensure @captures += 1 end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/compiler/000077500000000000000000000000001434172225000233365ustar00rootroot00000000000000rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/compiler/atom_subcompiler.rb000066400000000000000000000027351434172225000272360ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern class Compiler # Generates code that evaluates to a value (Ruby object) # This value responds to `===`. # # Doc on how this fits in the compiling process: # /docs/modules/ROOT/pages/node_pattern.adoc class AtomSubcompiler < Subcompiler private def visit_unify compiler.bind(node.child) do raise Invalid, 'unified variables can not appear first as argument' end end def visit_symbol node.child.inspect end alias visit_number visit_symbol alias visit_string visit_symbol alias visit_regexp visit_symbol def visit_const node.child end def visit_named_parameter compiler.named_parameter(node.child) end def visit_positional_parameter compiler.positional_parameter(node.child) end def visit_set set = node.children.map(&:child).to_set.freeze NodePattern::Sets[set] end # Assumes other types are node patterns. def visit_other_type compiler.with_temp_variables do |compare| code = compiler.compile_as_node_pattern(node, var: compare) "->(#{compare}) { #{code} }" end end end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/compiler/binding.rb000066400000000000000000000047751434172225000253120ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern class Compiler # Holds the list of bound variable names class Binding def initialize @bound = {} end # Yields the first time a given name is bound # # @return [String] bound variable name def bind(name) var = @bound.fetch(name) do yield n = @bound[name] = "unify_#{name.gsub('-', '__')}" n end if var == :forbidden_unification raise Invalid, "Wildcard #{name} was first seen in a subset of a " \ "union and can't be used outside that union" end var end # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def union_bind(enum) # We need to reset @bound before each branch is processed. # Moreover we need to keep track of newly encountered wildcards. # Var `newly_bound_intersection` will hold those that are encountered # in all branches; these are not a problem. # Var `partially_bound` 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? newly_bound_intersection = nil partially_bound = [] bound_before = @bound.dup result = enum.each do |e| @bound = bound_before.dup if newly_bound_intersection yield e newly_bound = @bound.keys - bound_before.keys if newly_bound_intersection.nil? # First iteration newly_bound_intersection = newly_bound else union = newly_bound_intersection | newly_bound newly_bound_intersection &= newly_bound partially_bound |= union - newly_bound_intersection end end # At this point, all members of `newly_bound_intersection` can be used # for unification outside of the union, but partially_bound may not forbid(partially_bound) result end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize private def forbid(names) names.each do |name| @bound[name] = :forbidden_unification end end end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/compiler/debug.rb000066400000000000000000000116351434172225000247570ustar00rootroot00000000000000# frozen_string_literal: true require 'rainbow' module RuboCop module AST class NodePattern class Compiler # Variant of the Compiler with tracing information for nodes class Debug < Compiler # Compiled node pattern requires a named parameter `trace`, # which should be an instance of this class class Trace def initialize @visit = {} end def enter(node_id) @visit[node_id] = false true end def success(node_id) @visit[node_id] = true end # return nil (not visited), false (not matched) or true (matched) def matched?(node_id) @visit[node_id] end end attr_reader :node_ids # @api private class Colorizer COLOR_SCHEME = { not_visitable: :lightseagreen, nil => :yellow, false => :red, true => :green }.freeze # Result of a NodePattern run against a particular AST # Consider constructor is private Result = Struct.new(:colorizer, :trace, :returned, :ruby_ast) do # @return [String] a Rainbow colorized version of ruby def colorize(color_scheme = COLOR_SCHEME) map = color_map(color_scheme) ast.loc.expression.source_buffer.source.chars.map.with_index do |char, i| Rainbow(char).color(map[i]) end.join end # @return [Hash] a map for {character_position => color} def color_map(color_scheme = COLOR_SCHEME) @color_map ||= match_map .transform_values { |matched| color_scheme.fetch(matched) } .map { |node, color| color_map_for(node, color) } .inject(:merge) .tap { |h| h.default = color_scheme.fetch(:not_visitable) } end # @return [Hash] a map for {node => matched?}, depth-first def match_map @match_map ||= ast .each_node .to_h { |node| [node, matched?(node)] } end # @return a value of `Trace#matched?` or `:not_visitable` def matched?(node) id = colorizer.compiler.node_ids.fetch(node) { return :not_visitable } trace.matched?(id) end private def color_map_for(node, color) return {} unless (range = node.loc&.expression) range.to_a.to_h { |char| [char, color] } end def ast colorizer.node_pattern.ast end end Compiler = Debug attr_reader :pattern, :compiler, :node_pattern def initialize(pattern, compiler: self.class::Compiler.new) @pattern = pattern @compiler = compiler @node_pattern = ::RuboCop::AST::NodePattern.new(pattern, compiler: @compiler) end # @return [Node] the Ruby AST def test(ruby, trace: self.class::Compiler::Trace.new) ruby = ruby_ast(ruby) if ruby.is_a?(String) returned = @node_pattern.as_lambda.call(ruby, trace: trace) self.class::Result.new(self, trace, returned, ruby) end private def ruby_ast(ruby) buffer = ::Parser::Source::Buffer.new('(ruby)', source: ruby) ruby_parser.parse(buffer) end def ruby_parser require 'parser/current' builder = ::RuboCop::AST::Builder.new ::Parser::CurrentRuby.new(builder) end end def initialize super @node_ids = Hash.new { |h, k| h[k] = h.size }.compare_by_identity end def named_parameters super << :trace end def parser @parser ||= Parser::WithMeta.new end def_delegators :parser, :comments, :tokens # @api private module InstrumentationSubcompiler def do_compile "#{tracer(:enter)} && #{super} && #{tracer(:success)}" end private def tracer(kind) "trace.#{kind}(#{node_id})" end def node_id compiler.node_ids[node] end end # @api private class NodePatternSubcompiler < Compiler::NodePatternSubcompiler include InstrumentationSubcompiler end # @api private class SequenceSubcompiler < Compiler::SequenceSubcompiler include InstrumentationSubcompiler end end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/compiler/node_pattern_subcompiler.rb000066400000000000000000000102401434172225000307460ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern class Compiler # Compiles code that evalues to true or false # for a given value `var` (typically a RuboCop::AST::Node) # or it's `node.type` if `seq_head` is true # # Doc on how this fits in the compiling process: # /docs/modules/ROOT/pages/node_pattern.adoc class NodePatternSubcompiler < Subcompiler attr_reader :access, :seq_head def initialize(compiler, var: nil, access: var, seq_head: false) super(compiler) @var = var @access = access @seq_head = seq_head end private def visit_negation expr = compile(node.child) "!(#{expr})" end def visit_ascend compiler.with_temp_variables do |ascend| expr = compiler.compile_as_node_pattern(node.child, var: ascend) "(#{ascend} = #{access_node}) && (#{ascend} = #{ascend}.parent) && #{expr}" end end def visit_descend compiler.with_temp_variables { |descendant| <<~RUBY.chomp } ::RuboCop::AST::NodePattern.descend(#{access}).any? do |#{descendant}| #{compiler.compile_as_node_pattern(node.child, var: descendant)} end RUBY end def visit_wildcard 'true' end def visit_unify name = compiler.bind(node.child) do |unify_name| # double assign to avoid "assigned but unused variable" return "(#{unify_name} = #{access_element}; #{unify_name} = #{unify_name}; true)" end compile_value_match(name) end def visit_capture "(#{compiler.next_capture} = #{access_element}; #{compile(node.child)})" end ### Lists def visit_union multiple_access(:union) do terms = compiler.each_union(node.children) .map { |child| compile(child) } "(#{terms.join(' || ')})" end end def visit_intersection multiple_access(:intersection) do node.children.map { |child| compile(child) } .join(' && ') end end def visit_predicate "#{access_element}.#{node.method_name}#{compile_args(node.arg_list)}" end def visit_function_call "#{node.method_name}#{compile_args(node.arg_list, first: access_element)}" end def visit_node_type "#{access_node}.#{node.child.to_s.tr('-', '_')}_type?" end def visit_sequence multiple_access(:sequence) do |var| term = compiler.compile_sequence(node, var: var) "#{compile_guard_clause} && #{term}" end end # Assumes other types are atoms. def visit_other_type value = compiler.compile_as_atom(node) compile_value_match(value) end # Compiling helpers def compile_value_match(value) "#{value} === #{access_element}" end # @param [Array, nil] # @return [String, nil] def compile_args(arg_list, first: nil) args = arg_list&.map { |arg| compiler.compile_as_atom(arg) } args = [first, *args] if first "(#{args.join(', ')})" if args end def access_element seq_head ? "#{access}.type" : access end def access_node return access if seq_head "#{compile_guard_clause} && #{access}" end def compile_guard_clause "#{access}.is_a?(::RuboCop::AST::Node)" end def multiple_access(kind) return yield @var if @var compiler.with_temp_variables(kind) do |var| memo = "#{var} = #{access}" @var = @access = var "(#{memo}; #{yield @var})" end end end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb000066400000000000000000000365441434172225000301130ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern class Compiler # Compiles terms within a sequence to code that evalues to true or false. # Compilation of the nodes that can match only a single term is deferred to # `NodePatternSubcompiler`; only nodes that can match multiple terms are # compiled here. # Assumes the given `var` is a `::RuboCop::AST::Node` # # Doc on how this fits in the compiling process: # /docs/modules/ROOT/pages/node_pattern.adoc # # rubocop:disable Metrics/ClassLength class SequenceSubcompiler < Subcompiler DELTA = 1 POSITIVE = :positive?.to_proc private_constant :POSITIVE # Calls `compile_sequence`; the actual `compile` method # will be used for the different terms of the sequence. # The only case of re-entrant call to `compile` is `visit_capture` def initialize(compiler, sequence:, var:) @seq = sequence # The node to be compiled @seq_var = var # Holds the name of the variable holding the AST::Node we are matching super(compiler) end def compile_sequence # rubocop:disable Layout/CommentIndentation compiler.with_temp_variables do |cur_child, cur_index, previous_index| @cur_child_var = cur_child # To hold the current child node @cur_index_var = cur_index # To hold the current child index (always >= 0) @prev_index_var = previous_index # To hold the child index before we enter the # variadic nodes @cur_index = :seq_head # Can be any of: # :seq_head : when the current child is actually the # sequence head # :variadic_mode : child index held by @cur_index_var # >= 0 : when the current child index is known # (from the beginning) # < 0 : when the index is known from the end, # where -1 is *past the end*, # -2 is the last child, etc... # This shift of 1 from standard Ruby indices # is stored in DELTA @in_sync = false # `true` iff `@cur_child_var` and `@cur_index_var` # correspond to `@cur_index` # Must be true if `@cur_index` is `:variadic_mode` compile_terms end # rubocop:enable Layout/CommentIndentation end private private :compile # Not meant to be called from outside # Single node patterns are all handled here def visit_other_type access = case @cur_index when :seq_head { var: @seq_var, seq_head: true } when :variadic_mode { var: @cur_child_var } else idx = @cur_index + (@cur_index.negative? ? DELTA : 0) { access: "#{@seq_var}.children[#{idx}]" } end term = compiler.compile_as_node_pattern(node, **access) compile_and_advance(term) end def visit_repetition within_loop do child_captures = node.child.nb_captures child_code = compile(node.child) next compile_loop(child_code) if child_captures.zero? compile_captured_repetition(child_code, child_captures) end end def visit_any_order within_loop do compiler.with_temp_variables do |matched| case_terms = compile_any_order_branches(matched) else_code, init = compile_any_order_else term = "#{compile_case(case_terms, else_code)} && #{compile_loop_advance}" all_matched_check = "&&\n#{matched}.size == #{node.term_nodes.size}" if node.rest_node <<~RUBY (#{init}#{matched} = {}; true) && #{compile_loop(term)} #{all_matched_check} \\ RUBY end end end def visit_union return visit_other_type if node.arity == 1 # The way we implement complex unions is by "forking", i.e. # making a copy of the present subcompiler to compile each branch # of the union. # We then use the resulting state of the subcompilers to # reset ourselves. forks = compile_union_forks preserve_union_start(forks) merge_forks!(forks) expr = forks.values.join(" || \n") "(#{expr})" end def compile_case(when_branches, else_code) <<~RUBY case #{when_branches.join(' ')} else #{else_code} end \\ RUBY end def compile_any_order_branches(matched_var) node.term_nodes.map.with_index do |node, i| code = compiler.compile_as_node_pattern(node, var: @cur_child_var, seq_head: false) var = "#{matched_var}[#{i}]" "when !#{var} && #{code} then #{var} = true" end end # @return [Array] Else code, and init code (if any) def compile_any_order_else rest = node.rest_node if !rest 'false' elsif rest.capture? capture_rest = compiler.next_capture init = "#{capture_rest} = [];" ["#{capture_rest} << #{@cur_child_var}", init] else 'true' end end def visit_capture return visit_other_type if node.child.arity == 1 storage = compiler.next_capture term = compile(node.child) capture = "#{@seq_var}.children[#{compile_matched(:range)}]" "#{term} && (#{storage} = #{capture})" end def visit_rest empty_loop end # Compilation helpers def compile_and_advance(term) case @cur_index when :variadic_mode "#{term} && #{compile_loop_advance}" when :seq_head # @in_sync = false # already the case @cur_index = 0 term else @in_sync = false @cur_index += 1 term end end def compile_captured_repetition(child_code, child_captures) captured_range = "#{compiler.captures - child_captures}...#{compiler.captures}" captured = "captures[#{captured_range}]" compiler.with_temp_variables do |accumulate| code = "#{child_code} && #{accumulate}.push(#{captured})" <<~RUBY (#{accumulate} = Array.new) && #{compile_loop(code)} && (#{captured} = if #{accumulate}.empty? (#{captured_range}).map{[]} # Transpose hack won't work for empty case else #{accumulate}.transpose end) \\ RUBY end end # Assumes `@cur_index` is already updated def compile_matched(kind) to = compile_cur_index from = if @prev_index == :variadic_mode @prev_index_used = true @prev_index_var else compile_index(@prev_index) end case kind when :range "#{from}...#{to}" when :length "#{to} - #{from}" end end def handle_prev @prev_index = @cur_index @prev_index_used = false code = yield if @prev_index_used @prev_index_used = false code = "(#{@prev_index_var} = #{@cur_index_var}; true) && #{code}" end code end def compile_terms(children = @seq.children, last_arity = 0..0) arities = remaining_arities(children, last_arity) total_arity = arities.shift guard = compile_child_nb_guard(total_arity) return guard if children.empty? @remaining_arity = total_arity terms = children.map do |child| use_index_from_end @remaining_arity = arities.shift handle_prev { compile(child) } end [guard, terms].join(" &&\n") end # yield `sync_code` iff not already in sync def sync return if @in_sync code = compile_loop_advance("= #{compile_cur_index}") @in_sync = true yield code end # @api private attr_reader :in_sync, :cur_index public :in_sync protected :cur_index, :compile_terms, :sync # @return [Array] total arities (as Ranges) of remaining children nodes # E.g. For sequence `(_ _? <_ _>)`, arities are: 1, 0..1, 2 # and remaining arities are: 3..4, 2..3, 2..2, 0..0 def remaining_arities(children, last_arity) last = last_arity arities = children .reverse .map(&:arity_range) .map { |r| last = last.begin + r.begin..last.max + r.max } .reverse! arities.push last_arity end # @return [String] code that evaluates to `false` if the matched arity is too small def compile_min_check return 'false' unless node.variadic? unless @remaining_arity.end.infinite? not_too_much_remaining = "#{compile_remaining} <= #{@remaining_arity.max}" end min_to_match = node.arity_range.begin if min_to_match.positive? enough_matched = "#{compile_matched(:length)} >= #{min_to_match}" end return 'true' unless not_too_much_remaining || enough_matched [not_too_much_remaining, enough_matched].compact.join(' && ') end def compile_remaining offset = case @cur_index when :seq_head ' + 1' when :variadic_mode " - #{@cur_index_var}" when 0 '' when POSITIVE " - #{@cur_index}" else # odd compiling condition, result may not be expected # E.g: `(... {a | b c})` => the b c branch can never match return - (@cur_index + DELTA) end "#{@seq_var}.children.size #{offset}" end def compile_max_matched return node.arity unless node.variadic? min_remaining_children = "#{compile_remaining} - #{@remaining_arity.begin}" return min_remaining_children if node.arity.end.infinite? "[#{min_remaining_children}, #{node.arity.max}].min" end def empty_loop @cur_index = -@remaining_arity.begin - DELTA @in_sync = false 'true' end def compile_cur_index return @cur_index_var if @in_sync compile_index end def compile_index(cur = @cur_index) return cur if cur >= 0 "#{@seq_var}.children.size - #{-(cur + DELTA)}" end # NOTE: assumes `@cur_index != :seq_head`. Node types using `within_loop` must # have `def in_sequence_head; :raise; end` def within_loop sync do |sync_code| @cur_index = :variadic_mode "#{sync_code} && #{yield}" end || yield end # returns truthy iff `@cur_index` switched to relative from end mode (i.e. < 0) def use_index_from_end return if @cur_index == :seq_head || @remaining_arity.begin != @remaining_arity.max @cur_index = -@remaining_arity.begin - DELTA end def compile_loop_advance(to = '+=1') # The `#{@cur_child_var} ||` is just to avoid unused variable warning "(#{@cur_child_var} = #{@seq_var}.children[#{@cur_index_var} #{to}]; " \ "#{@cur_child_var} || true)" end def compile_loop(term) <<~RUBY (#{compile_max_matched}).times do break #{compile_min_check} unless #{term} end \\ RUBY end def compile_child_nb_guard(arity_range) case arity_range.max when Float::INFINITY "#{compile_remaining} >= #{arity_range.begin}" when arity_range.begin "#{compile_remaining} == #{arity_range.begin}" else "(#{arity_range.begin}..#{arity_range.max}).cover?(#{compile_remaining})" end end # @return [Hash] of {subcompiler => code} def compile_union_forks compiler.each_union(node.children).to_h do |child| subsequence_terms = child.is_a?(Node::Subsequence) ? child.children : [child] fork = dup code = fork.compile_terms(subsequence_terms, @remaining_arity) @in_sync = false if @cur_index != :variadic_mode [fork, code] end end # Modifies in place `forks` to insure that `cur_{child|index}_var` are ok def preserve_union_start(forks) return if @cur_index != :variadic_mode || forks.size <= 1 compiler.with_temp_variables do |union_reset| cur = "(#{union_reset} = [#{@cur_child_var}, #{@cur_index_var}]) && " reset = "(#{@cur_child_var}, #{@cur_index_var} = #{union_reset}) && " forks.transform_values! do |code| code = "#{cur}#{code}" cur = reset code end end end # Modifies in place `forks` # Syncs our state def merge_forks!(forks) sub_compilers = forks.keys if !node.variadic? # e.g {a b | c d} @cur_index = sub_compilers.first.cur_index # all cur_index should be equivalent elsif use_index_from_end # nothing to do else # can't use index from end, so we must sync all forks @cur_index = :variadic_mode forks.each do |sub, code| sub.sync { |sync_code| forks[sub] = "#{code} && #{sync_code}" } end end @in_sync = sub_compilers.all?(&:in_sync) end end # rubocop:enable Metrics/ClassLength end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/compiler/subcompiler.rb000066400000000000000000000023621434172225000262120ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern class Compiler # Base class for subcompilers # Implements visitor pattern # # Doc on how this fits in the compiling process: # /docs/modules/ROOT/pages/node_pattern.adoc class Subcompiler attr_reader :compiler def initialize(compiler) @compiler = compiler @node = nil end def compile(node) prev = @node @node = node do_compile ensure @node = prev end # @api private private attr_reader :node def do_compile send(self.class.registry.fetch(node.type, :visit_other_type)) end @registry = {} class << self attr_reader :registry def method_added(method) @registry[Regexp.last_match(1).to_sym] = method if method =~ /^visit_(.*)/ super end def inherited(base) us = self base.class_eval { @registry = us.registry.dup } super end end end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/lexer.rb000066400000000000000000000030421434172225000231670ustar00rootroot00000000000000# frozen_string_literal: true begin require_relative 'lexer.rex' rescue LoadError msg = '*** You must run `rake generate` to generate the lexer and the parser ***' puts '*' * msg.length, msg, '*' * msg.length raise end module RuboCop module AST class NodePattern # Lexer class for `NodePattern` # # Doc on how this fits in the compiling process: # /docs/modules/ROOT/pages/node_pattern.adoc class Lexer < LexerRex Error = ScanError REGEXP_OPTIONS = { 'i' => ::Regexp::IGNORECASE, 'm' => ::Regexp::MULTILINE, 'x' => ::Regexp::EXTENDED, 'o' => 0 }.freeze private_constant :REGEXP_OPTIONS attr_reader :source_buffer, :comments, :tokens def initialize(source) @tokens = [] super() parse(source) end private # @return [token] def emit(type) value = ss[1] || ss.matched value = yield value if block_given? token = token(type, value) @tokens << token token end def emit_comment nil end def emit_regexp body = ss[1] options = ss[2] flag = options.each_char.sum { |c| REGEXP_OPTIONS[c] } emit(:tREGEXP) { Regexp.new(body, flag) } end def do_parse # Called by the generated `parse` method, do nothing here. end def token(type, value) [type, value] end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/lexer.rex000066400000000000000000000042131434172225000233630ustar00rootroot00000000000000# The only difficulty is to distinguish: `fn(argument)` from `fn (sequence)`. # The presence of the whitespace determines if it is an _argument_ to the # function call `fn` or if a _sequence_ follows the function call. # # If there is the potential for an argument list, the lexer enters the state `:ARG`. # The rest of the times, the state is `nil`. # # In case of an argument list, :tARG_LIST is emitted instead of a '('. # Therefore, the token '(' always signals the beginning of a sequence. class RuboCop::AST::NodePattern::LexerRex macros CONST_NAME /[A-Z:][a-zA-Z_:]+/ SYMBOL_NAME /[\w+@*\/?!<>=~|%^&-]+|\[\]=?/ IDENTIFIER /[a-z][a-zA-Z0-9_]*/ NODE_TYPE /[a-z][a-zA-Z0-9_-]*/ # Same as identifier but allows '-' CALL /(?:#{CONST_NAME}\.)?#{IDENTIFIER}[!?]?/ REGEXP_BODY /(?:[^\/]|\\\/)*/ REGEXP /\/(#{REGEXP_BODY})(? $ ! ^ ` ... + * ? ," )}/o { emit ss.matched, &:to_sym } /#{REGEXP}/o { emit_regexp } /%?(#{CONST_NAME})/o { emit :tPARAM_CONST } /%([a-z_]+)/ { emit :tPARAM_NAMED } /%(\d*)/ { emit(:tPARAM_NUMBER) { |s| s.empty? ? 1 : s.to_i } } # Map `%` to `%1` /_(#{IDENTIFIER})/o { emit :tUNIFY } /_/o { emit :tWILDCARD } /\#(#{CALL})/o { @state = :ARG; emit :tFUNCTION_CALL, &:to_sym } /#{IDENTIFIER}\?/o { @state = :ARG; emit :tPREDICATE, &:to_sym } /#{NODE_TYPE}/o { emit :tNODE_TYPE, &:to_sym } :ARG /\(/ { @state = nil; emit :tARG_LIST } :ARG // { @state = nil } /\#.*/ { emit_comment } end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/method_definer.rb000066400000000000000000000103631434172225000250300ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern # Functionality to turn `match_code` into methods/lambda module MethodDefiner def def_node_matcher(base, method_name, **defaults) def_helper(base, method_name, **defaults) do |name| params = emit_params('param0 = self') <<~RUBY def #{name}(#{params}) #{VAR} = param0 #{compile_init} #{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 compile_as_lambda <<~RUBY ->(#{emit_params('param0')}, block: nil) do #{VAR} = param0 #{compile_init} #{emit_lambda_code} end RUBY end def as_lambda eval(compile_as_lambda) # rubocop:disable Security/Eval end private # 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) method_name end def emit_node_search(method_name) if method_name.to_s.end_with?('?') on_match = 'return true' else args = emit_params(":#{method_name}", 'param0', forwarding: true) prelude = "return enum_for(#{args}) unless block_given?\n" on_match = emit_yield_capture(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('param0')}) #{compile_init} #{prelude} param0.each_node do |#{VAR}| if #{match_code} #{on_match} end end nil end RUBY end def emit_yield_capture(when_no_capture = '', yield_with: 'yield') 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_with}(#{yield_val})" end def emit_retval if captures.zero? 'true' elsif captures == 1 'captures[0]' else 'captures' end end def emit_param_list (1..positional_parameters).map { |n| "param#{n}" }.join(',') end def emit_keyword_list(forwarding: false) pattern = "%s: #{'%s' if forwarding}" named_parameters.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 emit_lambda_code <<~RUBY return unless #{match_code} block ? #{emit_yield_capture(yield_with: 'block.call')} : (return #{emit_retval}) RUBY end def compile_init "captures = Array.new(#{captures})" if captures.positive? end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/node.rb000066400000000000000000000145431434172225000230050ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern # Base class for AST Nodes of a `NodePattern` class Node < ::Parser::AST::Node extend Forwardable include ::RuboCop::AST::Descendence using Ext::RangeMinMax MATCHES_WITHIN_SET = %i[symbol number string].to_set.freeze private_constant :MATCHES_WITHIN_SET ### # To be overridden by subclasses ### def rest? false end def capture? false end # @return [Integer, Range] An Integer for fixed length terms, otherwise a Range. # Note: `arity.end` may be `Float::INFINITY` def arity 1 end # @return [Array, nil] replace node with result, or `nil` if no change requested. def in_sequence_head nil end ### # Utilities ### # @return [Array] def children_nodes children.grep(Node) end # @return [Node] most nodes have only one child def child children[0] end # @return [Integer] nb of captures of that node and its descendants def nb_captures children_nodes.sum(&:nb_captures) end # @return [Boolean] returns whether it matches a variable number of elements def variadic? arity.is_a?(Range) end # @return [Boolean] returns true for nodes having a Ruby literal equivalent # that matches within a Set (e.g. `42`, `:sym` but not `/regexp/`) def matches_within_set? MATCHES_WITHIN_SET.include?(type) end # @return [Range] arity as a Range def arity_range a = arity a.is_a?(Range) ? a : INT_TO_RANGE[a] end def with(type: @type, children: @children, location: @location) self.class.new(type, children, { location: location }) end INT_TO_RANGE = Hash.new { |h, k| h[k] = k..k } private_constant :INT_TO_RANGE # :nodoc: module ForbidInSeqHead def in_sequence_head raise NodePattern::Invalid, "A sequence can not start with a #{type}" end end ### # Subclasses for specific node types ### # Node class for `$something` class Capture < Node # Delegate most introspection methods to it's only child def_delegators :child, :arity, :rest? def capture? true end def nb_captures 1 + super end def in_sequence_head wildcard, original_child = child.in_sequence_head return unless original_child [wildcard, self] # ($...) => (_ $...) end end # Node class for `(type first second ...)` class Sequence < Node include ForbidInSeqHead def initialize(type, children = [], properties = {}) if (replace = children.first.in_sequence_head) children = [*replace, *children[1..]] end super end end # Node class for `predicate?(:arg, :list)` class Predicate < Node def method_name children.first end def arg_list children[1..] end end FunctionCall = Predicate # Node class for `int+` class Repetition < Node include ForbidInSeqHead def operator children[1] end ARITIES = { '*': 0..Float::INFINITY, '+': 1..Float::INFINITY, '?': 0..1 }.freeze def arity ARITIES[operator] end end # Node class for `...` class Rest < Node ARITY = (0..Float::INFINITY).freeze private_constant :ARITY def rest? true end def arity ARITY end def in_sequence_head [Node.new(:wildcard), self] end end # Node class for `` class AnyOrder < Node include ForbidInSeqHead ARITIES = Hash.new { |h, k| h[k] = k - 1..Float::INFINITY } private_constant :ARITIES def term_nodes ends_with_rest? ? children[0...-1] : children end def ends_with_rest? children.last.rest? end def rest_node children.last if ends_with_rest? end def arity return children.size unless ends_with_rest? ARITIES[children.size] end end # A list (potentially empty) of nodes; part of a Union class Subsequence < Node include ForbidInSeqHead def arity min, max = children.map(&:arity_range).map(&:minmax).transpose.map(&:sum) min == max ? min || 0 : min..max # NOTE: || 0 for empty case, where min == max == nil. end def in_sequence_head super if children.empty? return unless (replace = children.first.in_sequence_head) [with(children: [*replace, *children[1..]])] end end # Node class for `{ ... }` class Union < Node def arity minima, maxima = children.map(&:arity_range).map(&:minmax).transpose min = minima.min max = maxima.max min == max ? min : min..max end def in_sequence_head return unless children.any?(&:in_sequence_head) new_children = children.map do |child| next child unless (replace = child.in_sequence_head) if replace.size > 1 Subsequence.new(:subsequence, replace, loc: child.loc) else replace.first end end [with(children: new_children)] end end # Registry MAP = Hash.new(Node).merge!( sequence: Sequence, repetition: Repetition, rest: Rest, capture: Capture, predicate: Predicate, any_order: AnyOrder, function_call: FunctionCall, subsequence: Subsequence, union: Union ).freeze end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/parser.rb000066400000000000000000000035631434172225000233540ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'parser.racc' module RuboCop module AST class NodePattern # Parser for NodePattern # Note: class reopened in `parser.racc` # # Doc on how this fits in the compiling process: # /docs/modules/ROOT/pages/node_pattern.adoc class Parser < Racc::Parser extend Forwardable Builder = NodePattern::Builder Lexer = NodePattern::Lexer def initialize(builder = self.class::Builder.new) super() @builder = builder end ## # (Similar API to `parser` gem) # Parses a source and returns the AST. # # @param [Parser::Source::Buffer, String] source_buffer The source buffer to parse. # @return [NodePattern::Node] # def parse(source) @lexer = self.class::Lexer.new(source) do_parse rescue Lexer::Error => e raise NodePattern::Invalid, e.message ensure @lexer = nil # Don't keep references end def inspect "<##{self.class}>" end private def_delegators :@builder, :emit_list, :emit_unary_op, :emit_atom, :emit_capture, :emit_call, :emit_union def_delegators :@lexer, :next_token def enforce_unary(node) return node if node.arity == 1 detail = node.loc&.expression&.source || node.to_s raise NodePattern::Invalid, 'parse error, expected unary node pattern ' \ "but got expression matching multiple elements: #{detail}" end # Overrides Racc::Parser's method: def on_error(token, val, _vstack) detail = token_to_str(token) || '?' raise NodePattern::Invalid, "parse error on value #{val.inspect} (#{detail})" end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/parser.y000066400000000000000000000077321434172225000232230ustar00rootroot00000000000000class RuboCop::AST::NodePattern::Parser options no_result_var token tSYMBOL tNUMBER tSTRING tWILDCARD tPARAM_NAMED tPARAM_CONST tPARAM_NUMBER tFUNCTION_CALL tPREDICATE tNODE_TYPE tARG_LIST tUNIFY tREGEXP rule node_pattern # @return Node : node_pattern_no_union | union { enforce_unary(val[0]) } ; node_pattern_no_union # @return Node : '(' variadic_pattern_list ')' { emit_list :sequence, *val } | '[' node_pattern_list ']' { emit_list :intersection, *val } | '!' node_pattern { emit_unary_op :negation, *val } | '^' node_pattern { emit_unary_op :ascend, *val } | '`' node_pattern { emit_unary_op :descend, *val } | '$' node_pattern { emit_capture(*val) } | tFUNCTION_CALL args { emit_call :function_call, *val } | tPREDICATE args { emit_call :predicate, *val } | tNODE_TYPE { emit_call :node_type, *val } | atom ; atom # @return Node : tSYMBOL { emit_atom :symbol, *val } | tNUMBER { emit_atom :number, *val } | tSTRING { emit_atom :string, *val } | tPARAM_CONST { emit_atom :const, *val } | tPARAM_NAMED { emit_atom :named_parameter, *val } | tPARAM_NUMBER { emit_atom :positional_parameter, *val } | tREGEXP { emit_atom :regexp, *val } | tWILDCARD { emit_atom :wildcard, *val } | tUNIFY { emit_atom :unify, *val } ; union # @return Node : '{' separated_variadic_patterns '}' { emit_union(*val) } ; variadic_pattern # @return Node : node_pattern_no_union | union | node_pattern repetition { main, repeat_t = val emit_unary_op(:repetition, repeat_t, main, repeat_t) } | opt_capture '<' node_pattern_list opt_rest '>' { opt_capture, bracket, node_pattern_list, opt_rest, close_bracket = val node_pattern_list << opt_rest if opt_rest main = emit_list :any_order, bracket, node_pattern_list, close_bracket emit_capture(opt_capture, main) } | rest ; repetition # @return Token : '?' | '*' | '+' ; opt_capture # @return Token | nil : | '$' ; rest # @return Node : opt_capture '...' { emit_capture(val[0], emit_atom(:rest, val[1])) } ; opt_rest # @return Node | nil : | rest ; args # @return [Token, Array, Token] | nil : | tARG_LIST arg_list ')' { val } ; arg_list # @return Array : node_pattern { val } | arg_list ',' node_pattern { val[0] << val[2] } ; node_pattern_list # @return Array : node_pattern { val } | node_pattern_list node_pattern { val[0] << val[1] } ; variadic_pattern_list # @return Array : variadic_pattern { val } | variadic_pattern_list variadic_pattern { val[0] << val[1] } ; separated_variadic_patterns # @return Array> : { [[]] } | separated_variadic_patterns variadic_pattern { val[0].last << val[1]; val[0] } | separated_variadic_patterns '|' { val[0] << [] } ; end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/sets.rb000066400000000000000000000016411434172225000230310ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern # Utility to assign a set of values to a constant module Sets REGISTRY = Hash.new do |h, set| name = Sets.name(set).freeze Sets.const_set(name, set) h[set] = "::RuboCop::AST::NodePattern::Sets::#{name}" end MAX = 4 def self.name(set) elements = set elements = set.first(MAX - 1) << :etc if set.size > MAX name = elements.to_a.join('_').upcase.gsub(/[^A-Z0-9_]/, '') uniq("SET_#{name}") end def self.uniq(name) return name unless Sets.const_defined?(name) (2..Float::INFINITY).each do |i| uniq = "#{name}_#{i}" return uniq unless Sets.const_defined?(uniq) end end def self.[](set) REGISTRY[set] end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/node_pattern/with_meta.rb000066400000000000000000000074701434172225000240420ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST class NodePattern class Parser # Overrides Parser to use `WithMeta` variants and provide additional methods class WithMeta < Parser # Overrides Lexer to token locations and comments class Lexer < NodePattern::Lexer attr_reader :source_buffer def initialize(str_or_buffer) @source_buffer = if str_or_buffer.respond_to?(:source) str_or_buffer else ::Parser::Source::Buffer.new('(string)', source: str_or_buffer) end @comments = [] super(@source_buffer.source) end def token(type, value) super(type, [value, pos]) end def emit_comment @comments << Comment.new(pos) super end # @return [::Parser::Source::Range] last match's position def pos ::Parser::Source::Range.new(source_buffer, ss.pos - ss.matched_size, ss.pos) end end # Overrides Builder to emit nodes with locations class Builder < NodePattern::Builder def emit_atom(type, token) value, loc = token begin_l = loc.resize(1) end_l = loc.end.adjust(begin_pos: -1) begin_l = nil if begin_l.source.match?(/\w/) end_l = nil if end_l.source.match?(/\w/) n(type, [value], source_map(token, begin_t: begin_l, end_t: end_l)) end def emit_unary_op(type, operator_t = nil, *children) children[-1] = children[-1].first if children[-1].is_a?(Array) # token? map = source_map(children.first.loc.expression, operator_t: operator_t) n(type, children, map) end def emit_list(type, begin_t, children, end_t) expr = children.first.loc.expression.join(children.last.loc.expression) map = source_map(expr, begin_t: begin_t, end_t: end_t) n(type, children, map) end def emit_call(type, selector_t, args = nil) selector, = selector_t begin_t, arg_nodes, end_t = args map = source_map(selector_t, begin_t: begin_t, end_t: end_t, selector_t: selector_t) n(type, [selector, *arg_nodes], map) end private def n(type, children, source_map) super(type, children, { location: source_map }) end def loc(token_or_range) return token_or_range[1] if token_or_range.is_a?(Array) token_or_range end def join_exprs(left_expr, right_expr) left_expr.loc.expression .join(right_expr.loc.expression) end def source_map(token_or_range, begin_t: nil, end_t: nil, operator_t: nil, selector_t: nil) expression_l = loc(token_or_range) expression_l = expression_l.expression if expression_l.respond_to?(:expression) locs = [begin_t, end_t, operator_t, selector_t].map { |token| loc(token) } begin_l, end_l, operator_l, selector_l = locs expression_l = locs.compact.inject(expression_l, :join) ::Parser::Source::Map::Send.new(_dot_l = nil, selector_l, begin_l, end_l, expression_l) .with_operator(operator_l) end end attr_reader :comments, :tokens def do_parse r = super @comments = @lexer.comments @tokens = @lexer.tokens r end end end end end end rubocop-ast-1.24.0/lib/rubocop/ast/processed_source.rb000066400000000000000000000214561434172225000227460ustar00rootroot00000000000000# 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 # @api private 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 INVALID_LEVELS = %i[error fatal].freeze private_constant :INVALID_LEVELS 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_by_identity(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| INVALID_LEVELS.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 # 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 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/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength def parser_class(ruby_version) case ruby_version when 1.9 require 'parser/ruby19' Parser::Ruby19 when 2.0 require 'parser/ruby20' Parser::Ruby20 when 2.1 require 'parser/ruby21' Parser::Ruby21 when 2.2 require 'parser/ruby22' Parser::Ruby22 when 2.3 require 'parser/ruby23' Parser::Ruby23 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 when 3.1 require 'parser/ruby31' Parser::Ruby31 when 3.2 require 'parser/ruby32' Parser::Ruby32 else raise ArgumentError, "RuboCop found unknown Ruby version: #{ruby_version.inspect}" end end # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, 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 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-1.24.0/lib/rubocop/ast/rubocop_compatibility.rb000066400000000000000000000016261434172225000237760ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/sexp.rb000066400000000000000000000006051434172225000203470ustar00rootroot00000000000000# 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-1.24.0/lib/rubocop/ast/token.rb000066400000000000000000000043351434172225000205140ustar00rootroot00000000000000# 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 dot? type == :tDOT end def regexp_dots? %i[tDOT2 tDOT3].include?(type) end def rescue_modifier? type == :kRESCUE_MOD end def end? type == :kEND end def equal_sign? %i[tEQL tOP_ASGN].include?(type) end def new_line? type == :tNL end end end end rubocop-ast-1.24.0/lib/rubocop/ast/traversal.rb000066400000000000000000000151361434172225000214000ustar00rootroot00000000000000# frozen_string_literal: true 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 # Only for debugging. # @api private class DebugError < RuntimeError end TYPE_TO_METHOD = Hash.new { |h, type| h[type] = :"on_#{type}" } def walk(node) return if node.nil? send(TYPE_TO_METHOD[node.type], node) nil end # @api private module CallbackCompiler SEND = 'send(TYPE_TO_METHOD[child.type], child)' assign_code = 'child = node.children[%i]' code = "#{assign_code}\n#{SEND}" TEMPLATE = { skip: '', always: code, nil?: "#{code} if child" }.freeze def def_callback(type, *signature, arity: signature.size..signature.size, arity_check: ENV.fetch('RUBOCOP_DEBUG', nil) && self.arity_check(arity), body: self.body(signature, arity_check)) type, *aliases = type lineno = caller_locations(1, 1).first.lineno module_eval(<<~RUBY, __FILE__, lineno) # rubocop:disable Style/EvalWithLocation def on_#{type}(node) # def on_send(node) #{body} # # body ... nil # nil end # end RUBY aliases.each do |m| alias_method "on_#{m}", "on_#{type}" end end def body(signature, prelude) signature .map.with_index do |arg, i| TEMPLATE[arg].gsub('%i', i.to_s) end .unshift(prelude) .join("\n") end def arity_check(range) <<~RUBY n = node.children.size raise DebugError, [ 'Expected #{range} children, got', n, 'for', node.inspect ].join(' ') unless (#{range}).cover?(node.children.size) RUBY end end private_constant :CallbackCompiler extend CallbackCompiler send_code = CallbackCompiler::SEND ### arity == 0 no_children = %i[true false nil self cbase zsuper redo retry forward_args forwarded_args match_nil_pattern forward_arg forwarded_restarg forwarded_kwrestarg lambda empty_else kwnilarg __FILE__ __LINE__ __ENCODING__] ### arity == 0..1 opt_symbol_child = %i[restarg kwrestarg] opt_node_child = %i[splat kwsplat match_rest] ### arity == 1 literal_child = %i[int float complex rational str sym lvar ivar cvar gvar nth_ref back_ref arg blockarg shadowarg kwarg match_var] many_symbol_children = %i[regopt] node_child = %i[not match_current_line defined? arg_expr pin if_guard unless_guard match_with_trailing_comma] node_or_nil_child = %i[block_pass preexe postexe] NO_CHILD_NODES = (no_children + opt_symbol_child + literal_child).to_set.freeze private_constant :NO_CHILD_NODES # Used by Commissioner ### arity > 1 symbol_then_opt_node = %i[lvasgn ivasgn cvasgn gvasgn] symbol_then_node_or_nil = %i[optarg kwoptarg] node_then_opt_node = %i[while until module sclass] ### variable arity many_node_children = %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 break next match_as array_pattern array_pattern_with_tail hash_pattern const_pattern find_pattern index indexasgn procarg0 kwargs] many_opt_node_children = %i[case rescue resbody ensure for when case_match in_pattern irange erange match_pattern match_pattern_p] ### Callbacks for above def_callback no_children def_callback opt_symbol_child, :skip, arity: 0..1 def_callback opt_node_child, :nil?, arity: 0..1 def_callback literal_child, :skip def_callback node_child, :always def_callback node_or_nil_child, :nil? def_callback symbol_then_opt_node, :skip, :nil?, arity: 1..2 def_callback symbol_then_node_or_nil, :skip, :nil? def_callback node_then_opt_node, :always, :nil? def_callback many_symbol_children, :skip, arity_check: nil def_callback many_node_children, body: <<~RUBY node.children.each { |child| #{send_code} } RUBY def_callback many_opt_node_children, body: <<~RUBY node.children.each { |child| #{send_code} if child } RUBY ### Other particular cases def_callback :const, :nil?, :skip def_callback :casgn, :nil?, :skip, :nil?, arity: 2..3 def_callback :class, :always, :nil?, :nil? def_callback :def, :skip, :always, :nil? def_callback :op_asgn, :always, :skip, :always def_callback :if, :always, :nil?, :nil? def_callback :block, :always, :always, :nil? def_callback :numblock, :always, :skip, :nil? def_callback :defs, :always, :skip, :always, :nil? def_callback %i[send csend], body: <<~RUBY node.children.each_with_index do |child, i| next if i == 1 #{send_code} if child end RUBY ### generic processing of any other node (forward compatibility) defined = instance_methods(false) .grep(/^on_/) .map { |s| s.to_s[3..].to_sym } # :on_foo => :foo to_define = ::Parser::Meta::NODE_TYPES.to_a to_define -= defined to_define -= %i[numargs ident] # transient to_define -= %i[blockarg_expr restarg_expr] # obsolete to_define -= %i[objc_kwarg objc_restarg objc_varargs] # mac_ruby def_callback to_define, body: <<~RUBY node.children.each do |child| next unless child.class == Node #{send_code} end RUBY MISSING = to_define if ENV['RUBOCOP_DEBUG'] end end end rubocop-ast-1.24.0/lib/rubocop/ast/version.rb000066400000000000000000000001701434172225000210520ustar00rootroot00000000000000# frozen_string_literal: true module RuboCop module AST module Version STRING = '1.24.0' end end end rubocop-ast-1.24.0/rubocop-ast.gemspec000066400000000000000000000027401434172225000176420ustar00rootroot00000000000000# 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.6.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) + %w[ lib/rubocop/ast/node_pattern/parser.racc.rb lib/rubocop/ast/node_pattern/lexer.rex.rb ] s.extra_rdoc_files = ['LICENSE.txt', 'README.md'] s.homepage = 'https://github.com/rubocop/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/rubocop-ast/blob/master/CHANGELOG.md', 'source_code_uri' => 'https://github.com/rubocop/rubocop-ast/', 'documentation_uri' => 'https://docs.rubocop.org/rubocop-ast/', 'bug_tracker_uri' => 'https://github.com/rubocop/rubocop-ast/issues', 'rubygems_mfa_required' => 'true' } s.add_runtime_dependency('parser', '>= 3.1.1.0') s.add_development_dependency('bundler', '>= 1.15.0', '< 3.0') ##### Do NOT add `rubocop` (or anything depending on `rubocop`) here. See Gemfile end rubocop-ast-1.24.0/spec/000077500000000000000000000000001434172225000147665ustar00rootroot00000000000000rubocop-ast-1.24.0/spec/rubocop/000077500000000000000000000000001434172225000164375ustar00rootroot00000000000000rubocop-ast-1.24.0/spec/rubocop/ast/000077500000000000000000000000001434172225000172265ustar00rootroot00000000000000rubocop-ast-1.24.0/spec/rubocop/ast/alias_node_spec.rb000066400000000000000000000012741434172225000226670ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::AliasNode do subject(:alias_node) { parse_source(source).ast } describe '.new' do let(:source) do 'alias foo bar' end it { is_expected.to be_a(described_class) } end describe '#new_identifier' do let(:source) do 'alias foo bar' end it { expect(alias_node.new_identifier).to be_sym_type } 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).to be_sym_type } it { expect(alias_node.old_identifier.children.first).to eq(:bar) } end end rubocop-ast-1.24.0/spec/rubocop/ast/and_asgn_node_spec.rb000066400000000000000000000013541434172225000233470ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::AndAsgnNode do let(:or_asgn_node) { parse_source(source).ast } let(:source) { 'var &&= value' } describe '.new' do it { expect(or_asgn_node).to be_a(described_class) } end describe '#assignment_node' do subject { or_asgn_node.assignment_node } it { is_expected.to be_a(RuboCop::AST::AsgnNode) } end describe '#name' do subject { or_asgn_node.name } it { is_expected.to eq(:var) } end describe '#operator' do subject { or_asgn_node.operator } it { is_expected.to eq(:'&&') } end describe '#expression' do include AST::Sexp subject { or_asgn_node.expression } it { is_expected.to eq(s(:send, nil, :value)) } end end rubocop-ast-1.24.0/spec/rubocop/ast/and_node_spec.rb000066400000000000000000000056051434172225000223420ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::AndNode do subject(:and_node) { parse_source(source).ast } describe '.new' do context 'with a logical and node' do let(:source) do ':foo && :bar' end it { is_expected.to be_a(described_class) } end context 'with a semantic and node' do let(:source) do ':foo and :bar' end it { is_expected.to be_a(described_class) } end end describe '#logical_operator?' do context 'with a logical and node' do let(:source) do ':foo && :bar' end it { is_expected.to be_logical_operator } end context 'with a semantic and node' do let(:source) do ':foo and :bar' end it { is_expected.not_to be_logical_operator } end end describe '#semantic_operator?' do context 'with a logical and node' do let(:source) do ':foo && :bar' end it { is_expected.not_to be_semantic_operator } end context 'with a semantic and node' do let(:source) do ':foo and :bar' end it { is_expected.to be_semantic_operator } 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).to be_sym_type } end context 'with a semantic and node' do let(:source) do ':foo and 42' end it { expect(and_node.lhs).to be_sym_type } end end describe '#rhs' do context 'with a logical and node' do let(:source) do ':foo && 42' end it { expect(and_node.rhs).to be_int_type } end context 'with a semantic and node' do let(:source) do ':foo and 42' end it { expect(and_node.rhs).to be_int_type } end end end rubocop-ast-1.24.0/spec/rubocop/ast/arg_node_spec.rb000066400000000000000000000163241434172225000223510ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ArgNode do let(:args_node) { parse_source(source).ast.arguments } let(:arg_node) { args_node.first } describe '.new' do context 'with a method definition' do let(:source) { 'def foo(x) end' } it { expect(arg_node).to be_a(described_class) } end context 'with a block' do let(:source) { 'foo { |x| bar }' } if RuboCop::AST::Builder.emit_procarg0 it { expect(arg_node).to be_a(RuboCop::AST::Procarg0Node) } else it { expect(arg_node).to be_a(described_class) } end end context 'with a lambda literal' do let(:source) { '-> (x) { bar }' } it { expect(arg_node).to be_a(described_class) } end context 'with a keyword argument' do let(:source) { 'def foo(x:) end' } it { expect(arg_node).to be_a(described_class) } end context 'with an optional argument' do let(:source) { 'def foo(x = 42) end' } it { expect(arg_node).to be_a(described_class) } end context 'with an optional keyword argument' do let(:source) { 'def foo(x: 42) end' } it { expect(arg_node).to be_a(described_class) } end context 'with a splatted argument' do let(:source) { 'def foo(*x) end' } it { expect(arg_node).to be_a(described_class) } end context 'with a double splatted argument' do let(:source) { 'def foo(**x) end' } it { expect(arg_node).to be_a(described_class) } end context 'with a block argument' do let(:source) { 'def foo(&x) end' } it { expect(arg_node).to be_a(described_class) } end context 'with a shadow argument' do let(:source) { 'foo { |; x| }' } it { expect(arg_node).to be_a(described_class) } end context 'with argument forwarding' do context 'with Ruby >= 2.7', :ruby27 do let(:source) { 'def foo(...); end' } if RuboCop::AST::Builder.emit_forward_arg it { expect(arg_node).to be_a(described_class) } else it { expect(arg_node).to be_forward_args_type } end end context 'with Ruby >= 3.0', :ruby30 do let(:source) { 'def foo(x, ...); end' } let(:arg_node) { args_node.last } it { expect(arg_node).to be_a(described_class) } end end end describe '#name' do subject { arg_node.name } context 'with a regular argument' do let(:source) { 'def foo(x) end' } it { is_expected.to eq(:x) } end context 'with a block' do let(:source) { 'foo { |x| x }' } it { is_expected.to eq(:x) } end context 'with a keyword argument' do let(:source) { 'def foo(x:) end' } it { is_expected.to eq(:x) } end context 'with an optional argument' do let(:source) { 'def foo(x = 42) end' } it { is_expected.to eq(:x) } end context 'with an optional keyword argument' do let(:source) { 'def foo(x: 42) end' } it { is_expected.to eq(:x) } end context 'with a splatted argument' do let(:source) { 'def foo(*x) end' } it { is_expected.to eq(:x) } end context 'with a nameless splatted argument' do let(:source) { 'def foo(*) end' } it { is_expected.to be_nil } end context 'with a double splatted argument' do let(:source) { 'def foo(**x) end' } it { is_expected.to eq(:x) } end context 'with a nameless double splatted argument' do let(:source) { 'def foo(**) end' } it { is_expected.to be_nil } end context 'with a block argument' do let(:source) { 'def foo(&x) end' } it { is_expected.to eq(:x) } end context 'with a shadow argument' do let(:source) { 'foo { |; x| x = 5 }' } it { is_expected.to eq(:x) } end context 'with argument forwarding' do context 'with Ruby >= 2.7', :ruby27 do let(:source) { 'def foo(...); end' } it { is_expected.to be_nil } if RuboCop::AST::Builder.emit_forward_arg end context 'with Ruby >= 3.0', :ruby30 do let(:source) { 'def foo(x, ...); end' } let(:arg_node) { args_node.last } it { is_expected.to be_nil } end end end describe '#default_value' do include AST::Sexp subject { arg_node.default_value } context 'with a regular argument' do let(:source) { 'def foo(x) end' } it { is_expected.to be_nil } end context 'with a block' do let(:source) { 'foo { |x| x }' } it { is_expected.to be_nil } end context 'with an optional argument' do let(:source) { 'def foo(x = 42) end' } it { is_expected.to eq(s(:int, 42)) } end context 'with an optional keyword argument' do let(:source) { 'def foo(x: 42) end' } it { is_expected.to eq(s(:int, 42)) } end context 'with a splatted argument' do let(:source) { 'def foo(*x) end' } it { is_expected.to be_nil } end context 'with a double splatted argument' do let(:source) { 'def foo(**x) end' } it { is_expected.to be_nil } end context 'with a block argument' do let(:source) { 'def foo(&x) end' } it { is_expected.to be_nil } end context 'with a shadow argument' do let(:source) { 'foo { |; x| x = 5 }' } it { is_expected.to be_nil } end context 'with argument forwarding' do context 'with Ruby >= 2.7', :ruby27 do let(:source) { 'def foo(...); end' } it { is_expected.to be_nil } if RuboCop::AST::Builder.emit_forward_arg end context 'with Ruby >= 3.0', :ruby30 do let(:source) { 'def foo(x, ...); end' } let(:arg_node) { args_node.last } it { is_expected.to be_nil } end end end describe '#default?' do subject { arg_node.default? } context 'with a regular argument' do let(:source) { 'def foo(x) end' } it { is_expected.to be(false) } end context 'with a block' do let(:source) { 'foo { |x| x }' } it { is_expected.to be(false) } end context 'with an optional argument' do let(:source) { 'def foo(x = 42) end' } it { is_expected.to be(true) } end context 'with an optional keyword argument' do let(:source) { 'def foo(x: 42) end' } it { is_expected.to be(true) } end context 'with a splatted argument' do let(:source) { 'def foo(*x) end' } it { is_expected.to be(false) } end context 'with a double splatted argument' do let(:source) { 'def foo(**x) end' } it { is_expected.to be(false) } end context 'with a block argument' do let(:source) { 'def foo(&x) end' } it { is_expected.to be(false) } end context 'with a shadow argument' do let(:source) { 'foo { |; x| x = 5 }' } it { is_expected.to be(false) } end context 'with argument forwarding' do context 'with Ruby >= 2.7', :ruby27 do let(:source) { 'def foo(...); end' } it { is_expected.to be(false) } if RuboCop::AST::Builder.emit_forward_arg end context 'with Ruby >= 3.0', :ruby30 do let(:source) { 'def foo(x, ...); end' } let(:arg_node) { args_node.last } it { is_expected.to be(false) } end end end end rubocop-ast-1.24.0/spec/rubocop/ast/args_node_spec.rb000066400000000000000000000051471434172225000225350ustar00rootroot00000000000000# 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).to be_a(described_class) } end context 'with a block' do let(:source) { 'foo { |x| bar }' } it { expect(args_node).to be_a(described_class) } end context 'with a lambda literal' do let(:source) { '-> (x) { bar }' } it { expect(args_node).to be_a(described_class) } 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 describe '#argument_list' do include AST::Sexp subject { args_node.argument_list } let(:source) { 'foo { |a, b = 42, (c, *d), e:, f: 42, **g, &h; i| nil }' } let(:arguments) do [ s(:arg, :a), s(:optarg, :b, s(:int, 42)), s(:arg, :c), s(:restarg, :d), s(:kwarg, :e), s(:kwoptarg, :f, s(:int, 42)), s(:kwrestarg, :g), s(:blockarg, :h), s(:shadowarg, :i) ] end it { is_expected.to eq(arguments) } context 'when using Ruby 2.7 or newer', :ruby27 do context 'with argument forwarding' do let(:source) { 'def foo(...); end' } let(:arguments) { [s(:forward_arg)] } it { is_expected.to eq(arguments) } if RuboCop::AST::Builder.emit_forward_arg end end end end rubocop-ast-1.24.0/spec/rubocop/ast/array_node_spec.rb000066400000000000000000000053721434172225000227170ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ArrayNode do subject(:array_node) { parse_source(source).ast } describe '.new' do let(:source) { '[]' } it { is_expected.to be_a(described_class) } end describe '#values' do context 'with an empty array' do let(:source) { '[]' } it { expect(array_node.values).to be_empty } 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 { nil }).to be_a(described_class) } 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).to be_a(Enumerator) } end end describe '#square_brackets?' do context 'with square brackets' do let(:source) { '[1, 2, 3]' } it { is_expected.to be_square_brackets } end context 'with a percent literal' do let(:source) { '%w(foo bar)' } it { is_expected.not_to be_square_brackets } end end describe '#percent_literal?' do context 'with square brackets' do let(:source) { '[1, 2, 3]' } it { is_expected.not_to be_percent_literal } it { is_expected.not_to be_percent_literal(:string) } it { is_expected.not_to be_percent_literal(:symbol) } end context 'with a string percent literal' do let(:source) { '%w(foo bar)' } it { is_expected.to be_percent_literal } it { is_expected.to be_percent_literal(:string) } it { is_expected.not_to be_percent_literal(:symbol) } end context 'with a symbol percent literal' do let(:source) { '%i(foo bar)' } it { is_expected.to be_percent_literal } it { is_expected.not_to be_percent_literal(:string) } it { is_expected.to be_percent_literal(:symbol) } end end describe '#bracketed?' do context 'with square brackets' do let(:source) { '[1, 2, 3]' } it { is_expected.to be_bracketed } end context 'with a percent literal' do let(:source) { '%w(foo bar)' } it { is_expected.to be_bracketed } 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-1.24.0/spec/rubocop/ast/asgn_node_spec.rb000066400000000000000000000036111434172225000225230ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::AsgnNode do let(:asgn_node) { parse_source(source).ast } describe '.new' do context 'with a `lvasgn` node' do let(:source) { 'var = value' } it { expect(asgn_node).to be_a(described_class) } end context 'with a `ivasgn` node' do let(:source) { '@var = value' } it { expect(asgn_node).to be_a(described_class) } end context 'with a `cvasgn` node' do let(:source) { '@@var = value' } it { expect(asgn_node).to be_a(described_class) } end context 'with a `gvasgn` node' do let(:source) { '$var = value' } it { expect(asgn_node).to be_a(described_class) } end end describe '#name' do subject { asgn_node.name } context 'with a `lvasgn` node' do let(:source) { 'var = value' } it { is_expected.to eq(:var) } end context 'with a `ivasgn` node' do let(:source) { '@var = value' } it { is_expected.to eq(:@var) } end context 'with a `cvasgn` node' do let(:source) { '@@var = value' } it { is_expected.to eq(:@@var) } end context 'with a `gvasgn` node' do let(:source) { '$var = value' } it { is_expected.to eq(:$var) } end end describe '#expression' do include AST::Sexp subject { asgn_node.expression } context 'with a `lvasgn` node' do let(:source) { 'var = value' } it { is_expected.to eq(s(:send, nil, :value)) } end context 'with a `ivasgn` node' do let(:source) { '@var = value' } it { is_expected.to eq(s(:send, nil, :value)) } end context 'with a `cvasgn` node' do let(:source) { '@@var = value' } it { is_expected.to eq(s(:send, nil, :value)) } end context 'with a `gvasgn` node' do let(:source) { '$var = value' } it { is_expected.to eq(s(:send, nil, :value)) } end end end rubocop-ast-1.24.0/spec/rubocop/ast/block_node_spec.rb000066400000000000000000000154731434172225000226760ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::BlockNode do subject(:block_node) { parse_source(source).ast } describe '.new' do let(:source) { 'foo { |q| bar(q) }' } it { is_expected.to be_a(described_class) } end describe '#arguments' do context 'with no arguments' do let(:source) { 'foo { bar }' } it { expect(block_node.arguments).to be_empty } 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 'with destructured arguments' do let(:source) { 'foo { |q, (r, s)| bar(q, r, s) }' } 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).to be_empty } end end end describe '#argument_list' do subject(:argument_list) { block_node.argument_list } let(:names) { argument_list.map(&:name) } context 'with no arguments' do let(:source) { 'foo { bar }' } it { is_expected.to be_empty } end context 'all argument types' do let(:source) { 'foo { |a, b = 42, (c, *d), e:, f: 42, **g, &h; i| nil }' } it { expect(names).to eq(%i[a b c d e f g h i]) } end context '>= Ruby 2.7', :ruby27 do context 'using numbered parameters' do context 'with skipped params' do let(:source) { 'foo { _7 }' } it { expect(names).to eq(%i[_1 _2 _3 _4 _5 _6 _7]) } end context 'with sequential params' do let(:source) { 'foo { _1 + _2 }' } it { expect(names).to eq(%i[_1 _2]) } end end end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'foo { bar }' } it { is_expected.not_to be_arguments } end context 'with a single argument' do let(:source) { 'foo { |q| bar(q) }' } it { is_expected.to be_arguments } end context 'with a single splat argument' do let(:source) { 'foo { |*q| bar(q) }' } it { is_expected.to be_arguments } end context 'with multiple mixed arguments' do let(:source) { 'foo { |q, *z| bar(q, z) }' } it { is_expected.to be_arguments } end context 'with destructuring arguments' do let(:source) { 'foo { |(q, r)| bar(q, r) }' } it { is_expected.to be_arguments } end context '>= Ruby 2.7', :ruby27 do context 'using numbered parameters' do let(:source) { 'foo { _1 }' } it { is_expected.not_to be_arguments } end end end describe '#braces?' do context 'when enclosed in braces' do let(:source) { 'foo { bar }' } it { is_expected.to be_braces } end context 'when enclosed in do-end keywords' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { is_expected.not_to be_braces } end end describe '#keywords?' do context 'when enclosed in braces' do let(:source) { 'foo { bar }' } it { is_expected.not_to be_keywords } end context 'when enclosed in do-end keywords' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { is_expected.to be_keywords } end end describe '#lambda?' do context 'when block belongs to a stabby lambda' do let(:source) { '-> { bar }' } it { is_expected.to be_lambda } end context 'when block belongs to a method lambda' do let(:source) { 'lambda { bar }' } it { is_expected.to be_lambda } end context 'when block belongs to a non-lambda method' do let(:source) { 'foo { bar }' } it { is_expected.not_to be_lambda } 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 { is_expected.to be_single_line } end context 'when block is on several lines' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { is_expected.not_to be_single_line } end end describe '#multiline?' do context 'when block is on a single line' do let(:source) { 'foo { bar }' } it { is_expected.not_to be_multiline } end context 'when block is on several lines' do let(:source) do ['foo do', ' bar', 'end'].join("\n") end it { is_expected.to be_multiline } end end describe '#void_context?' do context 'when block method is each' do let(:source) { 'each { bar }' } it { is_expected.to be_void_context } end context 'when block method is tap' do let(:source) { 'tap { bar }' } it { is_expected.to be_void_context } end context 'when block method is not each' do let(:source) { 'map { bar }' } it { is_expected.not_to be_void_context } end end describe '#receiver' do context 'with dot operator call' do let(:source) { 'foo.bar { baz }' } it { expect(block_node.receiver.source).to eq('foo') } end context 'with safe navigation operator call' do let(:source) { 'foo&.bar { baz }' } it { expect(block_node.receiver.source).to eq('foo') } end end end rubocop-ast-1.24.0/spec/rubocop/ast/break_node_spec.rb000066400000000000000000000002541434172225000226570ustar00rootroot00000000000000# 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-1.24.0/spec/rubocop/ast/case_match_node_spec.rb000066400000000000000000000101461434172225000236630ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::CaseMatchNode do subject(: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 { is_expected.to be_a(described_class) } 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).to be_a(Enumerator) } 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 { is_expected.not_to be_else } end context 'with an else statement' do let(:source) do <<~RUBY case expr in pattern else end RUBY end it { is_expected.to be_else } 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).to be_nil } 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).to be_sym_type } end context 'with an empty else statement' do let(:source) do <<~RUBY case expr in pattern else end RUBY end it { expect(case_match_node.else_branch).to be_empty_else_type } end end end describe '#branches' do context 'when there is an else' do context 'with else body' do let(:source) { <<~RUBY } case pattern in :foo then # do nothing in :bar then 42 else 'hello' end RUBY it 'returns all the bodies' do expect(case_match_node.branches).to match [nil, be_int_type, be_str_type] end end context 'with empty else' do let(:source) { <<~RUBY } case pattern in :foo then # do nothing in :bar then 42 else # do nothing end RUBY it 'returns all the bodies' do expect(case_match_node.branches).to match [nil, be_int_type, nil] end end end context 'when there is no else keyword' do let(:source) { <<~RUBY } case pattern in :foo then # do nothing in :bar then 42 end RUBY it 'returns only then when bodies' do expect(case_match_node.branches).to match [nil, be_int_type] end end end end end rubocop-ast-1.24.0/spec/rubocop/ast/case_node_spec.rb000066400000000000000000000077021434172225000225130ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::CaseNode do subject(: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).to be_a(described_class) } 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).to be_a(Enumerator) } 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).not_to be_else } 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).to be_else } 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).to be_nil } 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).to be_sym_type } end context 'with an empty else statement' do let(:source) do <<~RUBY case when :foo then :bar else end RUBY end it { expect(case_node.else_branch).to be_nil } 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-1.24.0/spec/rubocop/ast/casgn_node_spec.rb000066400000000000000000000021341434172225000226650ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::CasgnNode do let(:casgn_node) { parse_source(source).ast } describe '.new' do context 'with a `casgn` node' do let(:source) { 'VAR = value' } it { expect(casgn_node).to be_a(described_class) } end end describe '#namespace' do include AST::Sexp subject { casgn_node.namespace } context 'when there is no parent' do let(:source) { 'VAR = value' } it { is_expected.to be_nil } end context 'when the parent is a `cbase`' do let(:source) { '::VAR = value' } it { is_expected.to eq(s(:cbase)) } end context 'when the parent is a `const`' do let(:source) { 'FOO::VAR = value' } it { is_expected.to eq(s(:const, nil, :FOO)) } end end describe '#name' do subject { casgn_node.name } let(:source) { 'VAR = value' } it { is_expected.to eq(:VAR) } end describe '#expression' do include AST::Sexp subject { casgn_node.expression } let(:source) { 'VAR = value' } it { is_expected.to eq(s(:send, nil, :value)) } end end rubocop-ast-1.24.0/spec/rubocop/ast/class_node_spec.rb000066400000000000000000000024531434172225000227030ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ClassNode do subject(:class_node) { parse_source(source).ast } describe '.new' do let(:source) do 'class Foo; end' end it { is_expected.to be_a(described_class) } end describe '#identifier' do let(:source) do 'class Foo; end' end it { expect(class_node.identifier).to be_const_type } 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).to be_const_type } 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).to be_send_type } end context 'with a multi-expression body' do let(:source) do 'class Foo; bar; baz; end' end it { expect(class_node.body).to be_begin_type } 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-1.24.0/spec/rubocop/ast/const_node_spec.rb000066400000000000000000000027221434172225000227230ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ConstNode do subject(: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).not_to be_module_name } context 'with a constant with a lowercase letter' do let(:source) { '::Foo::Bar' } it { expect(const_node).to be_module_name } end end describe '#absolute?' do it { expect(const_node).to be_absolute } context 'with a constant not starting with ::' do let(:source) { 'Foo::Bar::BAZ' } it { expect(const_node).not_to be_absolute } end context 'with a non-namespaced constant' do let(:source) { 'Foo' } it { expect(const_node).not_to be_absolute } end end describe '#relative?' do context 'with a non-namespaced constant' do let(:source) { 'Foo' } it { expect(const_node).to be_relative } 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-1.24.0/spec/rubocop/ast/def_node_spec.rb000066400000000000000000000311121434172225000223260ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::DefNode do subject(:def_node) { parse_source(source).ast } describe '.new' do context 'with a def node' do let(:source) { 'def foo(bar); end' } it { is_expected.to be_a(described_class) } end context 'with a defs node' do let(:source) { 'def self.foo(bar); end' } it { is_expected.to be_a(described_class) } 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 { is_expected.to be_method(:bar) } end context 'when argument is a string' do let(:source) { 'bar(:baz)' } it { is_expected.to be_method('bar') } end end context 'when message does not match' do context 'when argument is a symbol' do let(:source) { 'bar(:baz)' } it { is_expected.not_to be_method(:foo) } end context 'when argument is a string' do let(:source) { 'bar(:baz)' } it { is_expected.not_to be_method('foo') } end end end describe '#arguments' do context 'with no arguments' do let(:source) { 'def foo; end' } it { expect(def_node.arguments).to be_empty } 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).to be_nil } end context 'with a single regular argument' do let(:source) { 'def foo(bar); end' } it { expect(def_node.first_argument).to be_arg_type } end context 'with a single rest argument' do let(:source) { 'def foo(*bar); end' } it { expect(def_node.first_argument).to be_restarg_type } end context 'with a single keyword argument' do let(:source) { 'def foo(bar: :baz); end' } it { expect(def_node.first_argument).to be_kwoptarg_type } end context 'with multiple regular arguments' do let(:source) { 'def foo(bar, baz); end' } it { expect(def_node.first_argument).to be_arg_type } end context 'with multiple mixed arguments' do let(:source) { 'def foo(bar, *baz); end' } it { expect(def_node.first_argument).to be_arg_type } end end describe '#last_argument' do context 'with no arguments' do let(:source) { 'def foo; end' } it { expect(def_node.last_argument).to be_nil } end context 'with a single regular argument' do let(:source) { 'def foo(bar); end' } it { expect(def_node.last_argument).to be_arg_type } end context 'with a single rest argument' do let(:source) { 'def foo(*bar); end' } it { expect(def_node.last_argument).to be_restarg_type } end context 'with a single keyword argument' do let(:source) { 'def foo(bar: :baz); end' } it { expect(def_node.last_argument).to be_kwoptarg_type } end context 'with multiple regular arguments' do let(:source) { 'def foo(bar, baz); end' } it { expect(def_node.last_argument).to be_arg_type } end context 'with multiple mixed arguments' do let(:source) { 'def foo(bar, *baz); end' } it { expect(def_node.last_argument).to be_restarg_type } end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'def foo; end' } it { is_expected.not_to be_arguments } end context 'with a single regular argument' do let(:source) { 'def foo(bar); end' } it { is_expected.to be_arguments } end context 'with a single rest argument' do let(:source) { 'def foo(*bar); end' } it { is_expected.to be_arguments } end context 'with a single keyword argument' do let(:source) { 'def foo(bar: :baz); end' } it { is_expected.to be_arguments } end context 'with multiple regular arguments' do let(:source) { 'def foo(bar, baz); end' } it { is_expected.to be_arguments } end context 'with multiple mixed arguments' do let(:source) { 'def foo(bar, *baz); end' } it { is_expected.to be_arguments } end end describe '#rest_argument?' do context 'with a rest argument' do let(:source) { 'def foo(*bar); end' } it { is_expected.to be_rest_argument } end context 'with no arguments' do let(:source) { 'def foo; end' } it { is_expected.not_to be_rest_argument } end context 'with regular arguments' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_rest_argument } end context 'with mixed arguments' do let(:source) { 'def foo(bar, *baz); end' } it { is_expected.to be_rest_argument } end end describe '#operator_method?' do context 'with a binary operator method' do let(:source) { 'def ==(bar); end' } it { is_expected.to be_operator_method } end context 'with a unary operator method' do let(:source) { 'def -@; end' } it { is_expected.to be_operator_method } end context 'with a setter method' do let(:source) { 'def foo=(bar); end' } it { is_expected.not_to be_operator_method } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_operator_method } end end describe '#comparison_method?' do context 'with a comparison method' do let(:source) { 'def <=(bar); end' } it { is_expected.to be_comparison_method } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_comparison_method } end end describe '#assignment_method?' do context 'with an assignment method' do let(:source) { 'def foo=(bar); end' } it { is_expected.to be_assignment_method } end context 'with a bracket assignment method' do let(:source) { 'def []=(bar); end' } it { is_expected.to be_assignment_method } end context 'with a comparison method' do let(:source) { 'def ==(bar); end' } it { is_expected.not_to be_assignment_method } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_assignment_method } end end describe '#void_context?' do context 'with an initializer method' do let(:source) { 'def initialize(bar); end' } it { is_expected.to be_void_context } end context 'with a regular assignment method' do let(:source) { 'def foo=(bar); end' } it { is_expected.to be_void_context } end context 'with a bracket assignment method' do let(:source) { 'def []=(bar); end' } it { is_expected.to be_void_context } end context 'with a comparison method' do let(:source) { 'def ==(bar); end' } it { is_expected.not_to be_void_context } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_void_context } end end context 'when using Ruby 2.7 or newer', :ruby27 do describe '#argument_forwarding?' do let(:source) { 'def foo(...); end' } it { is_expected.to be_argument_forwarding } end end describe '#receiver' do context 'with an instance method definition' do let(:source) { 'def foo(bar); end' } it { expect(def_node.receiver).to be_nil } end context 'with a class method definition' do let(:source) { 'def self.foo(bar); end' } it { expect(def_node.receiver).to be_self_type } end context 'with a singleton method definition' do let(:source) { 'def Foo.bar(baz); end' } it { expect(def_node.receiver).to be_const_type } end end describe '#self_receiver?' do context 'with an instance method definition' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_self_receiver } end context 'with a class method definition' do let(:source) { 'def self.foo(bar); end' } it { is_expected.to be_self_receiver } end context 'with a singleton method definition' do let(:source) { 'def Foo.bar(baz); end' } it { is_expected.not_to be_self_receiver } end end describe '#const_receiver?' do context 'with an instance method definition' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_const_receiver } end context 'with a class method definition' do let(:source) { 'def self.foo(bar); end' } it { is_expected.not_to be_const_receiver } end context 'with a singleton method definition' do let(:source) { 'def Foo.bar(baz); end' } it { is_expected.to be_const_receiver } end end describe '#predicate_method?' do context 'with a predicate method' do let(:source) { 'def foo?(bar); end' } it { is_expected.to be_predicate_method } end context 'with a bang method' do let(:source) { 'def foo!(bar); end' } it { is_expected.not_to be_predicate_method } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_predicate_method } end end describe '#bang_method?' do context 'with a bang method' do let(:source) { 'def foo!(bar); end' } it { is_expected.to be_bang_method } end context 'with a predicate method' do let(:source) { 'def foo?(bar); end' } it { is_expected.not_to be_bang_method } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_bang_method } end end describe '#camel_case_method?' do context 'with a camel case method' do let(:source) { 'def Foo(bar); end' } it { is_expected.to be_camel_case_method } end context 'with a regular method' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_camel_case_method } end end describe '#block_argument?' do context 'with a block argument' do let(:source) { 'def foo(&bar); end' } it { is_expected.to be_block_argument } end context 'with no arguments' do let(:source) { 'def foo; end' } it { is_expected.not_to be_block_argument } end context 'with regular arguments' do let(:source) { 'def foo(bar); end' } it { is_expected.not_to be_block_argument } end context 'with mixed arguments' do let(:source) { 'def foo(bar, &baz); end' } it { is_expected.to be_block_argument } end end describe '#body' do context 'with no body' do let(:source) { 'def foo(bar); end' } it { expect(def_node.body).to be_nil } end context 'with a single expression body' do let(:source) { 'def foo(bar); baz; end' } it { expect(def_node.body).to be_send_type } end context 'with a multi-expression body' do let(:source) { 'def foo(bar); baz; qux; end' } it { expect(def_node.body).to be_begin_type } end end describe '#endless?' do context 'with standard method definition' do let(:source) { 'def foo; 42; end' } it { is_expected.not_to be_endless } end context 'with endless method definition', :ruby30 do let(:source) { 'def foo() = 42' } it { is_expected.to be_endless } end end end rubocop-ast-1.24.0/spec/rubocop/ast/defined_node_spec.rb000066400000000000000000000013501434172225000231670ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::DefinedNode do subject(:defined_node) { parse_source(source).ast } describe '.new' do context 'with a defined? node' do let(:source) { 'defined? :foo' } it { is_expected.to be_a(described_class) } end end describe '#receiver' do let(:source) { 'defined? :foo' } it { expect(defined_node.receiver).to be_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-1.24.0/spec/rubocop/ast/dstr_node_spec.rb000066400000000000000000000013721434172225000225510ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::DstrNode do subject(:dstr_node) { parse_source(source).ast } describe '#value' do subject { dstr_node.value } context 'with a multiline string' do let(:source) do <<~RUBY 'this is a multiline ' \ 'string' RUBY end it { is_expected.to eq('this is a multiline string') } end context 'with interpolation' do let(:source) do '"foo #{bar} baz"' end it { is_expected.to eq('foo #{bar} baz') } end context 'with implicit concatenation' do let(:source) do <<~RUBY 'foo ' 'bar ' 'baz' RUBY end it { is_expected.to eq('foo bar baz') } end end end rubocop-ast-1.24.0/spec/rubocop/ast/ensure_node_spec.rb000066400000000000000000000006671434172225000231040ustar00rootroot00000000000000# 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).to be_a(described_class) } end describe '#body' do let(:source) { 'begin; beginbody; ensure; :ensurebody; end' } it { expect(ensure_node.body).to be_sym_type } end end rubocop-ast-1.24.0/spec/rubocop/ast/ext/000077500000000000000000000000001434172225000200265ustar00rootroot00000000000000rubocop-ast-1.24.0/spec/rubocop/ast/ext/range_spec.rb000066400000000000000000000007321434172225000224630ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::Ext::Range do subject(:node) { parse_source(source).ast } let(:source) { <<~RUBY } [ 1, 2 ] RUBY 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-1.24.0/spec/rubocop/ast/ext/set_spec.rb000066400000000000000000000004001434172225000221520ustar00rootroot00000000000000# 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 be true end end # rubocop:enable RSpec/DescribeClass, Style/CaseEquality rubocop-ast-1.24.0/spec/rubocop/ast/fixtures/000077500000000000000000000000001434172225000210775ustar00rootroot00000000000000rubocop-ast-1.24.0/spec/rubocop/ast/fixtures/code_examples.rb000066400000000000000000000452571434172225000242510ustar00rootroot00000000000000# Extracted from `parser` gem. # Add the following code at the beginning of `def assert_parses`: # # File.open('./out.rb', 'a+') do |f| # f << code << "\n\n#----\n" if versions.include? '2.7' # end alias $a $b #---- alias $a $+ #---- bar unless foo #---- foo[1, 2] #---- Foo = 10 #---- !foo #---- case foo; in A then true; end #---- case foo; in A::B then true; end #---- case foo; in ::A then true; end #---- () #---- begin end #---- foo[:baz => 1,] #---- Bar::Foo = 10 #---- foo += meth rescue bar #---- def foo(_, _); end #---- def foo(_a, _a); end #---- def a b: return end #---- o = { a: 1 } #---- a b{c d}, :e do end #---- a b{c(d)}, :e do end #---- a b(c d), :e do end #---- a b(c(d)), :e do end #---- a b{c d}, 1 do end #---- a b{c(d)}, 1 do end #---- a b(c d), 1 do end #---- a b(c(d)), 1 do end #---- a b{c d}, 1.0 do end #---- a b{c(d)}, 1.0 do end #---- a b(c d), 1.0 do end #---- a b(c(d)), 1.0 do end #---- a b{c d}, 1.0r do end #---- a b{c(d)}, 1.0r do end #---- a b(c d), 1.0r do end #---- a b(c(d)), 1.0r do end #---- a b{c d}, 1.0i do end #---- a b{c(d)}, 1.0i do end #---- a b(c d), 1.0i do end #---- a b(c(d)), 1.0i do end #---- td (1_500).toString(); td.num do; end #---- -> do rescue; end #---- bar if foo #---- yield(foo) #---- yield foo #---- yield() #---- yield #---- next(foo) #---- next foo #---- next() #---- next #---- 1...2 #---- case foo; when 1, *baz; bar; when *foo; end #---- def foo(...); bar(...); end #---- def foo(...); super(...); end #---- def foo(...); end #---- super(foo) #---- super foo #---- super() #---- desc "foo" do end #---- next fun foo do end #---- def f(foo); end #---- def f(foo, bar); end #---- %i[] #---- %I() #---- [1, 2] #---- {a: if true then 42 end} #---- def f(*foo); end #---- <(a; foo, bar) { } #---- 42 #---- +42 #---- -42 #---- module ::Foo; end #---- module Bar::Foo; end #---- foo&.bar {} #---- fun(f bar) #---- begin meth end while foo #---- case foo; in 1; end #---- case foo; in ->{ 42 } then true; end #---- begin; meth; rescue; baz; else foo; ensure; bar end #---- super #---- m "#{[]}" #---- foo[1, 2] = 3 #---- foo + 1 #---- foo - 1 #---- foo * 1 #---- foo / 1 #---- foo % 1 #---- foo ** 1 #---- foo | 1 #---- foo ^ 1 #---- foo & 1 #---- foo <=> 1 #---- foo < 1 #---- foo <= 1 #---- foo > 1 #---- foo >= 1 #---- foo == 1 #---- foo != 1 #---- foo === 1 #---- foo =~ 1 #---- foo !~ 1 #---- foo << 1 #---- foo >> 1 #---- tap (proc do end) #---- def foo =begin =end end #---- :foo #---- :'foo' #---- fun(*bar) #---- fun(*bar, &baz) #---- m { _1 + _9 } #---- m do _1 + _9 end #---- -> { _1 + _9} #---- -> do _1 + _9 end #---- case foo; when 'bar', 'baz'; bar; end #---- case foo; in x, then nil; end #---- case foo; in *x then nil; end #---- case foo; in * then nil; end #---- case foo; in x, y then nil; end #---- case foo; in x, y, then nil; end #---- case foo; in x, *y, z then nil; end #---- case foo; in *x, y, z then nil; end #---- case foo; in 1, "a", [], {} then nil; end #---- for a in foo do p a; end #---- for a in foo; p a; end #---- until foo do meth end #---- until foo; meth end #---- def self.foo; end #---- def self::foo; end #---- def (foo).foo; end #---- def String.foo; end #---- def String::foo; end #---- self::A, foo = foo #---- ::A, foo = foo #---- fun(&bar) #---- foo[bar, :baz => 1,] #---- { 1 => 2 } #---- { 1 => 2, :foo => "bar" } #---- m def x(); end; 1.tap do end #---- true #---- case foo; when 'bar'; bar; end #---- def f(foo:); end #---- f{ |a| } #---- redo #---- __FILE__ #---- 42r #---- 42.1r #---- def f a, o=1, *r, &b; end #---- def f a, o=1, *r, p, &b; end #---- def f a, o=1, &b; end #---- def f a, o=1, p, &b; end #---- def f a, *r, &b; end #---- def f a, *r, p, &b; end #---- def f a, &b; end #---- def f o=1, *r, &b; end #---- def f o=1, *r, p, &b; end #---- def f o=1, &b; end #---- def f o=1, p, &b; end #---- def f *r, &b; end #---- def f *r, p, &b; end #---- def f &b; end #---- def f ; end #---- { } #---- p <<~E " y" x E #---- class A; _1; end #---- module A; _1; end #---- class << foo; _1; end #---- def self.m; _1; end #---- _1 #---- case foo; in [x] then nil; end #---- case foo; in [x,] then nil; end #---- case foo; in [x, y] then true; end #---- case foo; in [x, y,] then true; end #---- case foo; in [x, y, *] then true; end #---- case foo; in [x, y, *z] then true; end #---- case foo; in [x, *y, z] then true; end #---- case foo; in [x, *, y] then true; end #---- case foo; in [*x, y] then true; end #---- case foo; in [*, x] then true; end #---- case foo; in 1 | 2 then true; end #---- a += 1 #---- @a |= 1 #---- @@var |= 10 #---- def a; @@var |= 10; end #---- def foo() a:b end #---- def foo a:b end #---- f { || a:b } #---- fun (f bar) #---- __ENCODING__ #---- __ENCODING__ #---- [ 1 => 2 ] #---- [ 1, 2 => 3 ] #---- 'a\ b' #---- <<-'HERE' a\ b HERE #---- %q{a\ b} #---- "a\ b" #---- <<-"HERE" a\ b HERE #---- %{a\ b} #---- %Q{a\ b} #---- %w{a\ b} #---- %W{a\ b} #---- %i{a\ b} #---- %I{a\ b} #---- :'a\ b' #---- %s{a\ b} #---- :"a\ b" #---- /a\ b/ #---- %r{a\ b} #---- %x{a\ b} #---- `a\ b` #---- <<-`HERE` a\ b HERE #---- ->(scope) {}; scope #---- while class Foo; tap do end; end; break; end #---- while class Foo a = tap do end; end; break; end #---- while class << self; tap do end; end; break; end #---- while class << self; a = tap do end; end; break; end #---- meth until foo #---- break fun foo do end #---- foo #---- BEGIN { 1 } #---- unless foo then bar; else baz; end #---- unless foo; bar; else baz; end #---- proc {_1 = nil} #---- begin ensure end #---- case 1; in 2; 3; else; 4; end #---- case foo; in x if true; nil; end #---- case foo; in x unless true; nil; end #---- <<~FOO baz\ qux FOO #---- foo = raise(bar) rescue nil #---- foo += raise(bar) rescue nil #---- foo[0] += raise(bar) rescue nil #---- foo.m += raise(bar) rescue nil #---- foo::m += raise(bar) rescue nil #---- foo.C += raise(bar) rescue nil #---- foo::C ||= raise(bar) rescue nil #---- foo = raise bar rescue nil #---- foo += raise bar rescue nil #---- foo[0] += raise bar rescue nil #---- foo.m += raise bar rescue nil #---- foo::m += raise bar rescue nil #---- foo.C += raise bar rescue nil #---- foo::C ||= raise bar rescue nil #---- p p{p(p);p p}, tap do end #---- begin meth end until foo #---- m1 :k => m2 do; m3() do end; end #---- __LINE__ #---- if (bar); foo; end #---- foo.a = 1 #---- foo::a = 1 #---- foo.A = 1 #---- foo::A = 1 #---- $10 #---- 1 in [a]; a #---- true ? 1.tap do |n| p n end : 0 #---- false ? raise {} : tap {} #---- false ? raise do end : tap do end #---- Bar::Foo #---- a&.b = 1 #---- break(foo) #---- break foo #---- break() #---- break #---- if foo..bar; end #---- !(foo..bar) #---- Foo #---- END { 1 } #---- class Foo < Bar; end #---- begin; meth; rescue Exception; bar; end #---- fun { } #---- fun() { } #---- fun(1) { } #---- fun do end #---- case foo; when 'bar' then bar; end #---- begin; meth; rescue foo => ex; bar; end #---- @foo #---- if foo then bar end #---- fun (1).to_i #---- $var = 10 #---- /\xa8/n =~ "" #---- while not (true) do end #---- foo, bar = 1, 2 #---- (foo, bar) = 1, 2 #---- foo, bar, baz = 1, 2 #---- proc {_1 = nil} #---- _2 = 1 #---- proc {|_3|} #---- def x(_4) end #---- def _5; end #---- def self._6; end #---- meth rescue bar #---- self.a, self[1, 2] = foo #---- self::a, foo = foo #---- self.A, foo = foo #---- foo.a += m foo #---- foo::a += m foo #---- foo.A += m foo #---- foo::A += m foo #---- m { |foo| } #---- m { |(foo, bar)| } #---- module Foo; end #---- f{ } #---- f{ | | } #---- f{ |;a| } #---- f{ |; a | } #---- f{ || } #---- f{ |a| } #---- f{ |a, c| } #---- f{ |a,| } #---- f{ |a, &b| } #---- f{ |a, *s, &b| } #---- f{ |a, *, &b| } #---- f{ |a, *s| } #---- f{ |a, *| } #---- f{ |*s, &b| } #---- f{ |*, &b| } #---- f{ |*s| } #---- f{ |*| } #---- f{ |&b| } #---- f{ |a, o=1, o1=2, *r, &b| } #---- f{ |a, o=1, *r, p, &b| } #---- f{ |a, o=1, &b| } #---- f{ |a, o=1, p, &b| } #---- f{ |a, *r, p, &b| } #---- f{ |o=1, *r, &b| } #---- f{ |o=1, *r, p, &b| } #---- f{ |o=1, &b| } #---- f{ |o=1, p, &b| } #---- f{ |*r, p, &b| } #---- assert dogs #---- assert do: true #---- f x: -> do meth do end end #---- foo "#{(1+1).to_i}" do; end #---- alias :foo bar #---- ..100 #---- ...100 #---- case foo; in ^foo then nil; end #---- for a, b in foo; p a, b; end #---- t=1;(foo)?t:T #---- foo[1, 2] #---- f{ |a| } #---- !m foo #---- fun(:foo => 1) #---- fun(:foo => 1, &baz) #---- %W[foo #{bar}] #---- %W[foo #{bar}foo#@baz] #---- fun (1) #---- %w[] #---- %W() #---- `foobar` #---- case foo; in self then true; end #---- a, (b, c) = foo #---- ((b, )) = foo #---- class A < B end #---- bar def foo; self.each do end end #---- case foo; in "a": then true; end #---- case foo; in "#{ 'a' }": then true; end #---- case foo; in "#{ %q{a} }": then true; end #---- case foo; in "#{ %Q{a} }": then true; end #---- case foo; in "a": 1 then true; end #---- case foo; in "#{ 'a' }": 1 then true; end #---- case foo; in "#{ %q{a} }": 1 then true; end #---- case foo; in "#{ %Q{a} }": 1 then true; end #---- a&.b &&= 1 #---- "#{-> foo {}}" #---- -foo #---- +foo #---- ~foo #---- meth while foo #---- $+ #---- [1, *foo, 2] #---- [1, *foo] #---- [*foo] #---- f{ |foo: 1, bar: 2, **baz, &b| } #---- f{ |foo: 1, &b| } #---- f{ |**baz, &b| } #---- if foo...bar; end #---- !(foo...bar) #---- fun(foo, *bar) #---- fun(foo, *bar, &baz) #---- foo or bar #---- foo || bar #---- f{ |foo:| } #---- 1.33 #---- -1.33 #---- foo[1, 2] = 3 #---- def f foo = 1; end #---- def f(foo=1, bar=2); end #---- case foo; in A(1, 2) then true; end #---- case foo; in A(x:) then true; end #---- case foo; in A() then true; end #---- case foo; in A[1, 2] then true; end #---- case foo; in A[x:] then true; end #---- case foo; in A[] then true; end #---- meth (-1.3).abs #---- foo (-1.3).abs #---- foo[m bar] #---- m a + b do end #---- +2.0 ** 10 #---- -2 ** 10 #---- -2.0 ** 10 #---- class << foo; nil; end #---- def f (((a))); end #---- def f ((a, a1)); end #---- def f ((a, *r)); end #---- def f ((a, *r, p)); end #---- def f ((a, *)); end #---- def f ((a, *, p)); end #---- def f ((*r)); end #---- def f ((*r, p)); end #---- def f ((*)); end #---- def f ((*, p)); end #---- ->{ } #---- foo[bar,] #---- ->{ } #---- -> * { } #---- -> do end #---- m ->(a = ->{_1}) {a} #---- m ->(a: ->{_1}) {a} #---- %i[foo bar] #---- f (g rescue nil) #---- [/()\1/, ?#] #---- `foo#{bar}baz` #---- "#{1}" #---- %W"#{1}" #---- def f foo: ; end #---- def f foo: -1 ; end #---- foo, bar = m foo #---- foo.a &&= 1 #---- foo[0, 1] &&= 2 #---- ->(a) { } #---- -> (a) { } #---- fun(foo, :foo => 1) #---- fun(foo, :foo => 1, &baz) #---- foo[0, 1] += 2 #---- @@foo #---- @foo, @@bar = *foo #---- a, b = *foo, bar #---- a, *b = bar #---- a, *b, c = bar #---- a, * = bar #---- a, *, c = bar #---- *b = bar #---- *b, c = bar #---- * = bar #---- *, c, d = bar #---- 42i #---- 42ri #---- 42.1i #---- 42.1ri #---- case; when foo; 'foo'; end #---- f{ |a, b,| } #---- p begin 1.times do 1 end end #---- retry #---- p <<~E E #---- p <<~E E #---- p <<~E x E #---- p <<~E x y E #---- p <<~E x y E #---- p <<~E x y E #---- p <<~E x y E #---- p <<~E x y E #---- p <<~E x y E #---- p <<~E x y E #---- p <<~E x \ y E #---- p <<~E x \ y E #---- p <<~"E" x #{foo} E #---- p <<~`E` x #{foo} E #---- p <<~"E" x #{" y"} E #---- case foo; in 1..2 then true; end #---- case foo; in 1.. then true; end #---- case foo; in ..2 then true; end #---- case foo; in 1...2 then true; end #---- case foo; in 1... then true; end #---- case foo; in ...2 then true; end #---- begin; meth; rescue; foo; else; bar; end #---- m [] do end #---- m [], 1 do end #---- %w[foo bar] #---- return fun foo do end #---- fun (1 ) #---- /foo#{bar}baz/ #---- if (a, b = foo); end #---- foo.(1) #---- foo::(1) #---- 1.. #---- 1... #---- def foo(...); bar(...); end #---- /#{1}(?bar)/ =~ 'bar' #---- foo = meth rescue bar #---- begin; meth; ensure; bar; end #---- var = 10; var #---- begin; meth; rescue; foo; end #---- begin; meth; rescue => ex; bar; end #---- begin; meth; rescue => @ex; bar; end #---- case foo; in {} then true; end #---- case foo; in a: 1 then true; end #---- case foo; in { a: 1 } then true; end #---- case foo; in { a: 1, } then true; end #---- case foo; in a: then true; end #---- case foo; in **a then true; end #---- case foo; in ** then true; end #---- case foo; in a: 1, b: 2 then true; end #---- case foo; in a:, b: then true; end #---- case foo; in a: 1, _a:, ** then true; end #---- case foo; in {a: 1 } false ; end #---- case foo; in {a: 2} false ; end #---- case foo; in {Foo: 42 } false ; end #---- case foo; in a: {b:}, c: p c ; end #---- case foo; in {a: } true ; end #---- lambda{|;a|a} #---- -> (arg={}) {} #---- case [__FILE__, __LINE__ + 1, __ENCODING__] in [__FILE__, __LINE__, __ENCODING__] end #---- nil #---- def f (foo: 1, bar: 2, **baz, &b); end #---- def f (foo: 1, &b); end #---- def f **baz, &b; end #---- def f *, **; end #---- false #---- a ||= 1 #---- if foo then bar; else baz; end #---- if foo; bar; else baz; end #---- a b{c d}, "x" do end #---- a b(c d), "x" do end #---- a b{c(d)}, "x" do end #---- a b(c(d)), "x" do end #---- a b{c d}, /x/ do end #---- a b(c d), /x/ do end #---- a b{c(d)}, /x/ do end #---- a b(c(d)), /x/ do end #---- a b{c d}, /x/m do end #---- a b(c d), /x/m do end #---- a b{c(d)}, /x/m do end #---- a b(c(d)), /x/m do end #---- { foo: 2, **bar } #---- begin; meth; rescue; baz; ensure; bar; end #---- a&.b #---- super foo, bar do end #---- super do end #---- let () { m(a) do; end } #---- "foo#@a" "bar" #---- /#)/x #---- 'foobar' #---- %q(foobar) #---- not m foo #---- class Foo < a:b; end #---- ?a #---- foo ? 1 : 2 #---- def f(*); end #---- case foo; in **nil then true; end #---- foo.fun #---- foo::fun #---- foo::Fun() #---- if foo then bar; end #---- if foo; bar; end #---- return(foo) #---- return foo #---- return() #---- return #---- undef foo, :bar, :"foo#{1}" #---- f <<-TABLE do TABLE end #---- case foo; when 'bar'; bar; else baz; end #---- begin; rescue LoadError; else; end #---- foo += m foo #---- unless foo then bar; end #---- unless foo; bar; end #---- { foo: 2 } #---- fun (1) {} #---- foo.fun (1) {} #---- foo::fun (1) {} #---- if (bar; a, b = foo); end #---- meth do; foo; rescue; bar; end #---- foo, bar = meth rescue [1, 2] #---- '#@1' #---- '#@@1' #---- <<-'HERE' #@1 HERE #---- <<-'HERE' #@@1 HERE #---- %q{#@1} #---- %q{#@@1} #---- "#@1" #---- "#@@1" #---- <<-"HERE" #@1 HERE #---- <<-"HERE" #@@1 HERE #---- %{#@1} #---- %{#@@1} #---- %Q{#@1} #---- %Q{#@@1} #---- %w[ #@1 ] #---- %w[ #@@1 ] #---- %W[#@1] #---- %W[#@@1] #---- %i[ #@1 ] #---- %i[ #@@1 ] #---- %I[#@1] #---- %I[#@@1] #---- :'#@1' #---- :'#@@1' #---- %s{#@1} #---- %s{#@@1} #---- :"#@1" #---- :"#@@1" #---- /#@1/ #---- /#@@1/ #---- %r{#@1} #---- %r{#@@1} #---- %x{#@1} #---- %x{#@@1} #---- `#@1` #---- `#@@1` #---- <<-`HERE` #@1 HERE #---- <<-`HERE` #@@1 HERE #---- meth[] {} #---- "#@a #@@a #$a" #---- /source/im #---- foo && (a, b = bar) #---- foo || (a, b = bar) #---- if foo; bar; elsif baz; 1; else 2; end #---- def f(&block); end #---- m "#{}#{()}" #---- #---- a &&= 1 #---- ::Foo #---- class Foo; end #---- class Foo end #---- begin; meth; rescue Exception, foo; bar; end #---- 1..2 #---- case foo; in x then x; end #---- case 1; in 2; 3; else; end #---- -> a: 1 { } #---- -> a: { } #---- def foo; end #---- def String; end #---- def String=; end #---- def until; end #---- def BEGIN; end #---- def END; end #---- foo.fun bar #---- foo::fun bar #---- foo::Fun bar #---- @@var = 10 #---- a = b = raise :x #---- a += b = raise :x #---- a = b += raise :x #---- a += b += raise :x #---- p ->() do a() do end end #---- foo.a ||= 1 #---- foo[0, 1] ||= 2 #---- p -> { :hello }, a: 1 do end #---- foo[0, 1] += m foo #---- { 'foo': 2 } #---- { 'foo': 2, 'bar': {}} #---- f(a ? "a":1) #---- while foo do meth end #---- while foo; meth end #---- foo = bar, 1 #---- foo = *bar #---- foo = baz, *bar #---- m = -> *args do end #---- defined? foo #---- defined?(foo) #---- defined? @foo #---- A += 1 #---- ::A += 1 #---- B::A += 1 #---- def x; self::A ||= 1; end #---- def x; ::A ||= 1; end #---- foo.a += 1 #---- foo::a += 1 #---- foo.A += 1 #---- p <<~"E" x\n y E #---- case foo; in (1) then true; end #---- $foo #---- case; when foo; 'foo'; else 'bar'; end #---- a @b do |c|;end #---- p :foo, {a: proc do end, b: proc do end} #---- p :foo, {:a => proc do end, b: proc do end} #---- p :foo, {"a": proc do end, b: proc do end} #---- p :foo, {proc do end => proc do end, b: proc do end} #---- p :foo, {** proc do end, b: proc do end} #---- <<~E 1 \ 2 3 E #---- <<-E 1 \ 2 3 E #---- def f(**nil); end #---- m { |**nil| } #---- ->(**nil) {} #---- a # # .foo #---- a # # .foo #---- a # # &.foo #---- a # # &.foo #---- ::Foo = 10 #---- not foo #---- not(foo) #---- not() #---- :"foo#{bar}baz" #---- @var = 10 #---- "foo#{bar}baz" #---- case foo; in 1 => a then true; end #---- def f(foo: 1); end #---- a ? b & '': nil #---- meth 1 do end.fun bar #---- meth 1 do end.fun(bar) #---- meth 1 do end::fun bar #---- meth 1 do end::fun(bar) #---- meth 1 do end.fun bar do end #---- meth 1 do end.fun(bar) {} #---- meth 1 do end.fun {} #---- def f(**); end #---- foo and bar #---- foo && bar #---- !(a, b = foo) #---- def m; class << self; class C; end; end; end #---- def m; class << self; module M; end; end; end #---- def m; class << self; A = nil; end; end #---- begin foo!; bar! end #---- foo = m foo #---- foo = bar = m foo #---- def f(**foo); end #---- %I[foo #{bar}] #---- %I[foo#{bar}] #---- self #---- a = 1; a b: 1 #---- def foo raise; raise A::B, ''; end #---- /(?bar)/ =~ 'bar'; match #---- let (:a) { m do; end } #---- fun #---- fun! #---- fun(1) #---- fun () {} #---- if /wat/; end #---- !/wat/ #---- # coding:utf-8 "\xD0\xBF\xD1\x80\xD0\xBE\xD0\xB2\xD0\xB5\xD1\x80\xD0\xBA\xD0\xB0" #---- while def foo; tap do end; end; break; end #---- while def self.foo; tap do end; end; break; end #---- while def foo a = tap do end; end; break; end #---- while def self.foo a = tap do end; end; break; end rubocop-ast-1.24.0/spec/rubocop/ast/float_node_spec.rb000066400000000000000000000011511434172225000226750ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::FloatNode do subject(:float_node) { parse_source(source).ast } describe '.new' do let(:source) { '42.0' } it { is_expected.to be_a(described_class) } end describe '#sign?' do context 'explicit positive float' do let(:source) { '+42.0' } it { is_expected.to be_sign } end context 'explicit negative float' do let(:source) { '-42.0' } it { is_expected.to be_sign } end end describe '#value' do let(:source) do '1.5' end it { expect(float_node.value).to eq(1.5) } end end rubocop-ast-1.24.0/spec/rubocop/ast/for_node_spec.rb000066400000000000000000000032131434172225000223570ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ForNode do subject(:for_node) { parse_source(source).ast } describe '.new' do let(:source) { 'for foo in bar; baz; end' } it { is_expected.to be_a(described_class) } 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 { is_expected.to be_do } end context 'without a do keyword' do let(:source) { 'for foo in bar; baz; end' } it { is_expected.not_to be_do } end end describe '#void_context?' do context 'with a do keyword' do let(:source) { 'for foo in bar do baz; end' } it { is_expected.to be_void_context } end context 'without a do keyword' do let(:source) { 'for foo in bar; baz; end' } it { is_expected.to be_void_context } end end describe '#variable' do let(:source) { 'for foo in :bar; :baz; end' } it { expect(for_node.variable).to be_lvasgn_type } end describe '#collection' do let(:source) { 'for foo in :bar; baz; end' } it { expect(for_node.collection).to be_sym_type } end describe '#body' do let(:source) { 'for foo in bar; :baz; end' } it { expect(for_node.body).to be_sym_type } end describe '#post_condition_loop?' do let(:source) { 'for foo in bar; baz; end' } it { is_expected.not_to be_post_condition_loop } end describe '#loop_keyword?' do let(:source) { 'for foo in bar; baz; end' } it { is_expected.to be_loop_keyword } end end rubocop-ast-1.24.0/spec/rubocop/ast/forward_args_node_spec.rb000066400000000000000000000011461434172225000242540ustar00rootroot00000000000000# 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).to be_a(described_class) } end describe '#to_a' do it { expect(args_node.to_a).to contain_exactly(args_node) } end end end end rubocop-ast-1.24.0/spec/rubocop/ast/hash_node_spec.rb000066400000000000000000000126741434172225000225270ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::HashNode do subject(:hash_node) { parse_source(source).ast } describe '.new' do let(:source) { '{}' } it { is_expected.to be_a(described_class) } end describe '#pairs' do context 'with an empty hash' do let(:source) { '{}' } it { expect(hash_node.pairs).to be_empty } 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 { is_expected.to be_empty } end context 'with a hash containing pairs' do let(:source) { '{ a: 1, b: 2 }' } it { is_expected.not_to be_empty } end context 'with a hash containing a keyword splat' do let(:source) { '{ **foo }' } it { is_expected.not_to be_empty } end end describe '#keys' do context 'with an empty hash' do let(:source) { '{}' } it { expect(hash_node.keys).to be_empty } 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).to be_a(Enumerator) } 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).to be_empty } 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).to be_a(Enumerator) } 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).to be_a(Enumerator) } 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 { is_expected.to be_pairs_on_same_line } end context 'with no pairs on the same line' do let(:source) do ['{ a: 1,', ' b: 2 }'].join("\n") end it { is_expected.not_to be_pairs_on_same_line } end context 'with some pairs on the same line' do let(:source) do ['{ a: 1,', ' b: 2, c: 3 }'].join("\n") end it { is_expected.to be_pairs_on_same_line } end end describe '#mixed_delimiters?' do context 'when all pairs are using a colon delimiter' do let(:source) { '{ a: 1, b: 2 }' } it { is_expected.not_to be_mixed_delimiters } end context 'when all pairs are using a hash rocket delimiter' do let(:source) { '{ :a => 1, :b => 2 }' } it { is_expected.not_to be_mixed_delimiters } end context 'when pairs are using different delimiters' do let(:source) { '{ :a => 1, b: 2 }' } it { is_expected.to be_mixed_delimiters } end end describe '#braces?' do context 'with braces' do let(:source) { '{ a: 1, b: 2 }' } it { is_expected.to be_braces } 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).not_to be_braces } 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).to be_braces } end end end rubocop-ast-1.24.0/spec/rubocop/ast/if_node_spec.rb000066400000000000000000000255411434172225000221770ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::IfNode do subject(:if_node) { parse_source(source).ast } describe '.new' do context 'with a regular if statement' do let(:source) { 'if foo?; :bar; end' } it { is_expected.to be_a(described_class) } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { is_expected.to be_a(described_class) } end context 'with a modifier statement' do let(:source) { ':foo if bar?' } it { is_expected.to be_a(described_class) } 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 { is_expected.to be_if } end context 'with an unless statement' do let(:source) { 'unless foo?; :bar; end' } it { is_expected.not_to be_if } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { is_expected.not_to be_if } end end describe '#unless?' do context 'with an if statement' do let(:source) { 'if foo?; :bar; end' } it { is_expected.not_to be_unless } end context 'with an unless statement' do let(:source) { 'unless foo?; :bar; end' } it { is_expected.to be_unless } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { is_expected.not_to be_unless } end end describe '#ternary?' do context 'with an if statement' do let(:source) { 'if foo?; :bar; end' } it { is_expected.not_to be_ternary } end context 'with an unless statement' do let(:source) { 'unless foo?; :bar; end' } it { is_expected.not_to be_ternary } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { is_expected.to be_ternary } 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).to be_elsif } end context 'with an if statement comtaining an elsif' do let(:source) do ['if foo?', ' 1', 'elsif bar?', ' 2', 'end'].join("\n") end it { is_expected.not_to be_elsif } end context 'without an elsif statement' do let(:source) do ['if foo?', ' 1', 'end'].join("\n") end it { is_expected.not_to be_elsif } 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 { is_expected.to be_else } end context 'without an else statement' do let(:source) do ['if foo?', ' 1', 'else', ' 2', 'end'].join("\n") end it { is_expected.not_to be_elsif } end end describe '#modifier_form?' do context 'with a non-modifier if statement' do let(:source) { 'if foo?; :bar; end' } it { is_expected.not_to be_modifier_form } end context 'with a non-modifier unless statement' do let(:source) { 'unless foo?; :bar; end' } it { is_expected.not_to be_modifier_form } end context 'with a ternary operator' do let(:source) { 'foo? ? :bar : :baz' } it { is_expected.not_to be_modifier_form } end context 'with a modifier if statement' do let(:source) { ':bar if foo?' } it { is_expected.to be_modifier_form } end context 'with a modifier unless statement' do let(:source) { ':bar unless foo?' } it { is_expected.to be_modifier_form } 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 { is_expected.not_to be_nested_conditional } 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 { is_expected.to be_nested_conditional } 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 { is_expected.to be_nested_conditional } 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 { is_expected.to be_nested_conditional } end context 'with nested ternary operators' do context 'when nested in the truthy branch' do let(:source) { 'foo? ? bar? ? 1 : 2 : 3' } it { is_expected.to be_nested_conditional } end context 'when nested in the falsey branch' do let(:source) { 'foo? ? 3 : bar? ? 1 : 2' } it { is_expected.to be_nested_conditional } 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 { is_expected.to be_elsif_conditional } 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 { is_expected.to be_elsif_conditional } 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 { is_expected.not_to be_elsif_conditional } 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 { is_expected.not_to be_elsif_conditional } end context 'with nested ternary operators' do context 'when nested in the truthy branch' do let(:source) { 'foo? ? bar? ? 1 : 2 : 3' } it { is_expected.not_to be_elsif_conditional } end context 'when nested in the falsey branch' do let(:source) { 'foo? ? 3 : bar? ? 1 : 2' } it { is_expected.not_to be_elsif_conditional } 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).to be_sym_type } 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).to be_sym_type } end context 'with a ternary operator' do let(:source) { 'foo? ? :foo : 42' } it { expect(if_node.if_branch).to be_sym_type } 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).to be_int_type } 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).to be_int_type } end context 'with a ternary operator' do let(:source) { 'foo? ? :foo : 42' } it { expect(if_node.else_branch).to be_int_type } 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 a ternary operator' do let(:source) { 'foo? ? :foo : 42' } it { expect(if_node.branches.size).to eq(2) } 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).to be_a(Enumerator) } 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-1.24.0/spec/rubocop/ast/in_pattern_node_spec.rb000066400000000000000000000071771434172225000237510ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::InPatternNode do context 'when using Ruby 2.7 or newer', :ruby27 do let(:in_pattern_node) { parse_source(source).ast.children[1] } describe '.new' do let(:source) do ['case condition', 'in [42] then foo', 'end'].join("\n") end it { expect(in_pattern_node).to be_a(described_class) } end describe '#pattern' do context 'with a value pattern' do let(:source) do ['case condition', 'in 42 then foo', 'end'].join("\n") end it { expect(in_pattern_node.pattern).to be_int_type } end context 'with a variable pattern' do let(:source) do ['case condition', 'in var then foo', 'end'].join("\n") end it { expect(in_pattern_node.pattern).to be_match_var_type } end context 'with an alternative pattern' do let(:source) do ['case condition', 'in :foo | :bar | :baz then foo', 'end'].join("\n") end it { expect(in_pattern_node.pattern).to be_match_alt_type } end context 'with an as pattern' do let(:source) do ['case condition', 'in Integer => var then foo', 'end'].join("\n") end it { expect(in_pattern_node.pattern).to be_match_as_type } end context 'with an array pattern' do let(:source) do ['case condition', 'in :foo, :bar, :baz then foo', 'end'].join("\n") end it { expect(in_pattern_node.pattern).to be_array_pattern_type } end context 'with a hash pattern' do let(:source) do ['case condition', 'in foo:, bar:, baz: then foo', 'end'].join("\n") end it { expect(in_pattern_node.pattern).to be_hash_pattern_type } end context 'with a pin operator', :ruby31 do let(:source) do ['case condition', 'in ^(2 + 2) then foo', 'end'].join("\n") end it { expect(in_pattern_node.pattern).to be_pin_type } end end describe '#then?' do context 'with a then keyword' do let(:source) do ['case condition', 'in [42] then foo', 'end'].join("\n") end it { expect(in_pattern_node).to be_then } end context 'without a then keyword' do let(:source) do ['case condition', 'in [42]', ' foo', 'end'].join("\n") end it { expect(in_pattern_node).not_to be_then } end end describe '#body' do context 'with a then keyword' do let(:source) do ['case condition', 'in [42] then :foo', 'end'].join("\n") end it { expect(in_pattern_node.body).to be_sym_type } end context 'without a then keyword' do let(:source) do ['case condition', 'in [42]', ' [:foo, :bar]', 'end'].join("\n") end it { expect(in_pattern_node.body).to be_array_type } end end describe '#branch_index' do let(:source) do ['case condition', 'in [42] then 1', 'in [43] then 2', 'in [44] then 3', 'end'].join("\n") end let(:in_patterns) { parse_source(source).ast.children[1...-1] } it { expect(in_patterns[0].branch_index).to eq(0) } it { expect(in_patterns[1].branch_index).to eq(1) } it { expect(in_patterns[2].branch_index).to eq(2) } end end end rubocop-ast-1.24.0/spec/rubocop/ast/int_node_spec.rb000066400000000000000000000011271434172225000223650ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::IntNode do subject(:int_node) { parse_source(source).ast } describe '.new' do let(:source) { '42' } it { is_expected.to be_a(described_class) } end describe '#sign?' do context 'explicit positive int' do let(:source) { '+42' } it { is_expected.to be_sign } end context 'explicit negative int' do let(:source) { '-42' } it { is_expected.to be_sign } end end describe '#value' do let(:source) do '10' end it { expect(int_node.value).to eq(10) } end end rubocop-ast-1.24.0/spec/rubocop/ast/keyword_splat_node_spec.rb000066400000000000000000000214161434172225000244650ustar00rootroot00000000000000# 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).to be_a(described_class) } end describe '#hash_rocket?' do let(:source) { '{ a: 1, **foo }' } it { expect(kwsplat_node).not_to be_hash_rocket } end describe '#colon?' do let(:source) { '{ a: 1, **foo }' } it { expect(kwsplat_node).not_to be_colon } 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).to be_same_line(second_pair) } end context 'when a multiline pair shares the same line' do let(:source) do ['{', ' a: (', ' ), **foo', '}'].join("\n") end it { expect(first_pair).to be_same_line(second_pair) } it { expect(second_pair).to be_same_line(first_pair) } end context 'when pairs are on separate lines' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair).not_to be_same_line(second_pair) } 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-1.24.0/spec/rubocop/ast/lambda_node_spec.rb000066400000000000000000000007721434172225000230200ustar00rootroot00000000000000# 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 be_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-1.24.0/spec/rubocop/ast/module_node_spec.rb000066400000000000000000000016541434172225000230650ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::ModuleNode do subject(:module_node) { parse_source(source).ast } describe '.new' do let(:source) do 'module Foo; end' end it { is_expected.to be_a(described_class) } end describe '#identifier' do let(:source) do 'module Foo; end' end it { expect(module_node.identifier).to be_const_type } end describe '#body' do context 'with a single expression body' do let(:source) do 'module Foo; bar; end' end it { expect(module_node.body).to be_send_type } end context 'with a multi-expression body' do let(:source) do 'module Foo; bar; baz; end' end it { expect(module_node.body).to be_begin_type } 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-1.24.0/spec/rubocop/ast/next_node_spec.rb000066400000000000000000000002521434172225000225470ustar00rootroot00000000000000# 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-1.24.0/spec/rubocop/ast/node_pattern/000077500000000000000000000000001434172225000217105ustar00rootroot00000000000000rubocop-ast-1.24.0/spec/rubocop/ast/node_pattern/helper.rb000066400000000000000000000011221434172225000235100ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'parse_helper' Failure = Struct.new(:expected, :actual) module NodePatternHelper include ParseHelper def assert_equal(expected, actual, mess = nil) expect(actual).to eq(expected), *mess end def assert(test, mess = nil) expect(test).to be(true), *mess end def expect_parsing(ast, source, source_maps) version = '-' try_parsing(ast, source, parser, source_maps, version) end end RSpec.shared_context 'parser' do include NodePatternHelper let(:parser) { RuboCop::AST::NodePattern::Parser::WithMeta.new } end rubocop-ast-1.24.0/spec/rubocop/ast/node_pattern/lexer_spec.rb000066400000000000000000000042321434172225000243670ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::NodePattern::Lexer do let(:source) { '(send nil? #func(:foo) #func (bar))' } let(:lexer) { RuboCop::AST::NodePattern::Parser::WithMeta::Lexer.new(source) } let(:tokens) do tokens = [] while (token = lexer.next_token) tokens << token end tokens end it 'provides tokens via next_token' do # rubocop:disable RSpec/ExampleLength type, (text, range) = tokens[3] expect(type).to eq :tFUNCTION_CALL expect(text).to eq :func expect(range.to_range).to eq 11...16 expect(tokens.map(&:first)).to eq [ '(', :tNODE_TYPE, :tPREDICATE, :tFUNCTION_CALL, :tARG_LIST, :tSYMBOL, ')', :tFUNCTION_CALL, '(', :tNODE_TYPE, ')', ')' ] end context 'with $type+' do let(:source) { '(array sym $int+ x)' } it 'is parsed as `$ int + x`' do expect(tokens.map(&:last).map(&:first)).to eq \ %i[( array sym $ int + x )] end end [ /test/, /[abc]+\/()?/x, # rubocop:disable Style/RegexpLiteral /back\\slash/ ].each do |regexp| context "when given a regexp #{regexp.inspect}" do let(:source) { regexp.inspect } it 'round trips' do token = tokens.first value = token.last.first expect(value.inspect).to eq regexp.inspect end end end context 'when given a regexp ending with a backslash' do let(:source) { '/tricky\\/' } it 'does not lexes it properly' do expect { tokens }.to raise_error(RuboCop::AST::NodePattern::LexerRex::ScanError) end end context 'when given node types and constants' do let(:source) { '(aa bb Cc DD ::Ee Ff::GG %::Hh Zz %Zz)' } let(:tokens) { super()[1...-1] } it 'distinguishes them' do types = tokens.map(&:first) expect(types).to eq ([:tNODE_TYPE] * 2) + ([:tPARAM_CONST] * 7) zz, percent_zz = tokens.last(2).map(&:last).map(&:first) expect(zz).to eq 'Zz' expect(percent_zz).to eq 'Zz' end end context 'when given arithmetic symbols' do let(:source) { ':&' } it 'is parsed as `:&`' do expect(tokens.map(&:last).map(&:first)).to eq [:&] end end end rubocop-ast-1.24.0/spec/rubocop/ast/node_pattern/parse_helper.rb000066400000000000000000000232651434172225000247160ustar00rootroot00000000000000# frozen_string_literal: true # Copied from Parser, some lines commented out with `# !!!` module ParseHelper include AST::Sexp # !!! require 'parser/all' # !!! require 'parser/macruby' # !!! require 'parser/rubymotion' ALL_VERSIONS = %w(1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 mac ios) def setup @diagnostics = [] super if defined?(super) end def parser_for_ruby_version(version) case version when '1.8' then parser = Parser::Ruby18.new when '1.9' then parser = Parser::Ruby19.new when '2.0' then parser = Parser::Ruby20.new when '2.1' then parser = Parser::Ruby21.new when '2.2' then parser = Parser::Ruby22.new when '2.3' then parser = Parser::Ruby23.new when '2.4' then parser = Parser::Ruby24.new when '2.5' then parser = Parser::Ruby25.new when '2.6' then parser = Parser::Ruby26.new when '2.7' then parser = Parser::Ruby27.new when '2.8' then parser = Parser::Ruby28.new when 'mac' then parser = Parser::MacRuby.new when 'ios' then parser = Parser::RubyMotion.new else raise "Unrecognized Ruby version #{version}" end parser.diagnostics.consumer = lambda do |diagnostic| @diagnostics << diagnostic end parser end def with_versions(versions) (versions & ALL_VERSIONS).each do |version| @diagnostics.clear parser = parser_for_ruby_version(version) yield version, parser end end def assert_source_range(expect_range, range, version, what) if expect_range == nil # Avoid "Use assert_nil if expecting nil from .... This will fail in Minitest 6."" assert_nil range, "(#{version}) range of #{what}" else assert range.is_a?(Parser::Source::Range), "(#{version}) #{range.inspect}.is_a?(Source::Range) for #{what}" assert_equal expect_range, range.to_range, "(#{version}) range of #{what}" end end # Use like this: # ~~~ # assert_parses( # s(:send, s(:lit, 10), :+, s(:lit, 20)) # %q{10 + 20}, # %q{~~~~~~~ expression # | ^ operator # | ~~ expression (lit) # }, # %w(1.8 1.9) # optional # ) # ~~~ def assert_parses(ast, code, source_maps='', versions=ALL_VERSIONS) with_versions(versions) do |version, parser| try_parsing(ast, code, parser, source_maps, version) end # Also try parsing with lexer set to use UTF-32LE internally with_versions(versions) do |version, parser| parser.instance_eval { @lexer.force_utf32 = true } try_parsing(ast, code, parser, source_maps, version) end end def try_parsing(ast, code, parser, source_maps, version) source_file = Parser::Source::Buffer.new('(assert_parses)', source: code) begin parsed_ast = parser.parse(source_file) rescue => exc backtrace = exc.backtrace Exception.instance_method(:initialize).bind(exc). call("(#{version}) #{exc.message}") exc.set_backtrace(backtrace) raise end if ast.nil? assert_nil parsed_ast, "(#{version}) AST equality" return end assert_equal ast, parsed_ast, "(#{version}) AST equality" parse_source_map_descriptions(source_maps) do |range, map_field, ast_path, line| astlet = traverse_ast(parsed_ast, ast_path) if astlet.nil? # This is a testsuite bug. raise "No entity with AST path #{ast_path} in #{parsed_ast.inspect}" end assert astlet.frozen? assert astlet.location.respond_to?(map_field), "(#{version}) #{astlet.location.inspect}.respond_to?(#{map_field.inspect}) for:\n#{parsed_ast.inspect}" found_range = astlet.location.send(map_field) assert_source_range(range, found_range, version, line.inspect) end # !!! assert parser.instance_eval { @lexer }.cmdarg.empty?, # !!! "(#{version}) expected cmdarg to be empty after parsing" # !!! assert_equal 0, parser.instance_eval { @lexer.instance_eval { @paren_nest } }, # !!! "(#{version}) expected paren_nest to be 0 after parsing" end # Use like this: # ~~~ # assert_diagnoses( # [:warning, :ambiguous_prefix, { prefix: '*' }], # %q{foo *bar}, # %q{ ^ location # | ~~~ highlights (0)}) # ~~~ def assert_diagnoses(diagnostic, code, source_maps='', versions=ALL_VERSIONS) with_versions(versions) do |version, parser| source_file = Parser::Source::Buffer.new('(assert_diagnoses)', source: code) begin parser = parser.parse(source_file) rescue Parser::SyntaxError # do nothing; the diagnostic was reported end assert_equal 1, @diagnostics.count, "(#{version}) emits a single diagnostic, not\n" \ "#{@diagnostics.map(&:render).join("\n")}" emitted_diagnostic = @diagnostics.first level, reason, arguments = diagnostic arguments ||= {} message = Parser::Messages.compile(reason, arguments) assert_equal level, emitted_diagnostic.level assert_equal reason, emitted_diagnostic.reason assert_equal arguments, emitted_diagnostic.arguments assert_equal message, emitted_diagnostic.message parse_source_map_descriptions(source_maps) do |range, map_field, ast_path, line| case map_field when 'location' assert_source_range range, emitted_diagnostic.location, version, 'location' when 'highlights' index = ast_path.first.to_i assert_source_range range, emitted_diagnostic.highlights[index], version, "#{index}th highlight" else raise "Unknown diagnostic range #{map_field}" end end end end # Use like this: # ~~~ # assert_diagnoses_many( # [ # [:warning, :ambiguous_literal], # [:error, :unexpected_token, { :token => :tLCURLY }] # ], # %q{m /foo/ {}}, # SINCE_2_4) # ~~~ def assert_diagnoses_many(diagnostics, code, versions=ALL_VERSIONS) with_versions(versions) do |version, parser| source_file = Parser::Source::Buffer.new('(assert_diagnoses_many)', source: code) begin parser = parser.parse(source_file) rescue Parser::SyntaxError # do nothing; the diagnostic was reported end assert_equal diagnostics.count, @diagnostics.count diagnostics.zip(@diagnostics) do |expected_diagnostic, actual_diagnostic| level, reason, arguments = expected_diagnostic arguments ||= {} message = Parser::Messages.compile(reason, arguments) assert_equal level, actual_diagnostic.level assert_equal reason, actual_diagnostic.reason assert_equal arguments, actual_diagnostic.arguments assert_equal message, actual_diagnostic.message end end end def refute_diagnoses(code, versions=ALL_VERSIONS) with_versions(versions) do |version, parser| source_file = Parser::Source::Buffer.new('(refute_diagnoses)', source: code) begin parser = parser.parse(source_file) rescue Parser::SyntaxError # do nothing; the diagnostic was reported end assert_empty @diagnostics, "(#{version}) emits no diagnostics, not\n" \ "#{@diagnostics.map(&:render).join("\n")}" end end def assert_context(context, code, versions=ALL_VERSIONS) with_versions(versions) do |version, parser| source_file = Parser::Source::Buffer.new('(assert_context)', source: code) parsed_ast = parser.parse(source_file) nodes = find_matching_nodes(parsed_ast) { |node| node.type == :send && node.children[1] == :get_context } assert_equal 1, nodes.count, "there must exactly 1 `get_context()` call" node = nodes.first assert_equal context, node.context, "(#{version}) expect parsing context to match" end end SOURCE_MAP_DESCRIPTION_RE = /(?x) ^(?# $1 skip) ^(\s*) (?# $2 highlight) ([~\^]+|\!) \s+ (?# $3 source_map_field) ([a-z_]+) (?# $5 ast_path) (\s+\(([a-z_.\/0-9]+)\))? $/ def parse_source_map_descriptions(descriptions) unless block_given? return to_enum(:parse_source_map_descriptions, descriptions) end descriptions.each_line do |line| # Remove leading " |", if it exists. line = line.sub(/^\s*\|/, '').rstrip next if line.empty? if (match = SOURCE_MAP_DESCRIPTION_RE.match(line)) if match[2] != '!' begin_pos = match[1].length end_pos = begin_pos + match[2].length range = begin_pos...end_pos end source_map_field = match[3] if match[5] ast_path = match[5].split('.') else ast_path = [] end yield range, source_map_field, ast_path, line else raise "Cannot parse source map description line: #{line.inspect}." end end end def traverse_ast(ast, path) path.inject(ast) do |astlet, path_component| # Split "dstr/2" to :dstr and 1 type_str, index_str = path_component.split('/') type = type_str.to_sym if index_str.nil? index = 0 else index = index_str.to_i - 1 end matching_children = \ astlet.children.select do |child| AST::Node === child && child.type == type end matching_children[index] end end def find_matching_nodes(ast, &block) return [] unless ast.is_a?(AST::Node) result = [] result << ast if block.call(ast) ast.children.each { |child| result += find_matching_nodes(child, &block) } result end end rubocop-ast-1.24.0/spec/rubocop/ast/node_pattern/parser_spec.rb000066400000000000000000000050741434172225000245510ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' RSpec.describe RuboCop::AST::NodePattern::Parser do include_context 'parser' describe 'sequences' do it 'parses simple sequences properly' do expect_parsing( s(:sequence, s(:node_type, :int), s(:number, 42)), '(int 42)', '^ begin | ^ end |~~~~~~~~ expression | ~~~ expression (node_type) | ~~ expression (number)' ) end it 'parses capture vs repetition with correct priority' do s_int = s(:capture, s(:node_type, :int)) s_str = s(:capture, s(:node_type, :str)) expect_parsing( s(:sequence, s(:wildcard, '_'), s(:repetition, s_int, :*), s(:repetition, s(:sequence, s_str), :+)), '(_ $int* ($str)+)', '^ begin | ^ end |~~~~~~~~~~~~~~~~~ expression | ~~~~~ expression (repetition) | ^ operator (repetition) | ^ operator (repetition.capture) | ~~~ expression (repetition.capture.node_type)' ) end it 'parses function calls' do expect_parsing( s(:function_call, :func, s(:number, 1), s(:number, 2), s(:number, 3)), '#func(1, 2, 3)', ' ^ begin | ^ end |~~~~~ selector | ^ expression (number)' ) end it 'expands ... in sequence head deep inside unions' do rest = s(:rest, :'...') expect_parsing( s(:sequence, s(:union, s(:node_type, :a), s(:subsequence, s(:node_type, :b), rest), s(:subsequence, s(:wildcard), rest, s(:node_type, :c)), s(:subsequence, s(:wildcard), s(:capture, rest)))), '({a | b ... | ... c | $...})', '' ) end it 'parses unions of literals as a set' do expect_parsing( s(:sequence, s(:set, s(:symbol, :a), s(:number, 42), s(:string, 'hello'))), '({:a 42 "hello"})', ' ^ begin (set) | ^ end (set) | ~~ expression (set/1.number)' ) end it 'generates specialized nodes' do source_file = Parser::Source::Buffer.new('(spec)', source: '($_)') ast = parser.parse(source_file) expect(ast.class).to eq ::RuboCop::AST::NodePattern::Node::Sequence expect(ast.child.class).to eq ::RuboCop::AST::NodePattern::Node::Capture end end end rubocop-ast-1.24.0/spec/rubocop/ast/node_pattern/sets_spec.rb000066400000000000000000000007741434172225000242350ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::NodePattern::Sets do subject(:name) { described_class[set] } let(:set) { Set[1, 2, 3, 4, 5, 6] } it { is_expected.to eq '::RuboCop::AST::NodePattern::Sets::SET_1_2_3_ETC' } it { is_expected.to eq described_class[Set[6, 5, 4, 3, 2, 1]] } it { is_expected.not_to eq described_class[Set[1, 2, 3, 4, 5, 6, 7]] } it 'creates a constant with the right value' do expect(eval(name)).to eq set # rubocop:disable Security/Eval end end rubocop-ast-1.24.0/spec/rubocop/ast/node_pattern_spec.rb000066400000000000000000001702171434172225000232570ustar00rootroot00000000000000# 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 be true expect(instance == 42).to be false expect(instance == described_class.new('(send)')).to be false expect(instance == described_class.new('(send 42 :to_s)')).to be 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 context 'variadic' do context 'with fixed terms' do it 'works for cases with fixed arity before and after union' do expect('(_ { int | sym _ str | } const)').to match_codes( '[X]', '[42, X]', '[:foo, //, "bar", X]' ).and not_match_codes( '[42]', '[4.2, X]', '["bar", //, :foo, X]' ) end it 'works for cases with variadic terms after union' do expect('(_ { int | sym _ str | } const+)').to match_codes( '[X]', '[42, X, Y, Z]', '[:foo, //, "bar", X]' ).and not_match_codes( '[42]', '[4.2, X]', '["bar", //, :foo, X]' ) end it 'works for cases with variadic terms before and after union' do expect('(_ const ? { int | sym _ str | } const+)').to match_codes( '[X]', '[FOO, 42, X, Y, Z]', '[:foo, //, "bar", X]', '[X, Y, Z]' ).and not_match_codes( '[42]', '[4.2, X]', '["bar", //, :foo, X]', '[FOO BAR, 42]' ) end end context 'with variadic terms' do it 'works for cases with fixed arity before and after union' do expect('(_ { sym+ _ str | int* } const)').to match_codes( '[X]', '[42, 666, X]', '[:foo, :foo2, //, "bar", X]' ).and not_match_codes( '[42]', '[4.2, X]', '["bar", //, :foo, X]' ) end it 'works for cases with variadic terms after union' do expect('(_ { sym+ _ str | int* } const+)').to match_codes( '[X]', '[42, 666, X, Y, Z]', '[:foo, :foo2, //, "bar", X]' ).and not_match_codes( '[42]', '[4.2, X]', '["bar", //, :foo, X]' ) end it 'works for cases with variadic terms before and after union' do expect('(_ const ? { sym+ _ str | int* } const+)').to match_codes( '[X]', '[FOO, 42, 666, X, Y, Z]', '[:foo, :foo2, //, "bar", X]', '[X, Y, Z]' ).and not_match_codes( '[42]', '[4.2, X]', '["bar", //, :foo, X]', '[FOO BAR, 42]' ) end end context 'multiple' do it 'works for complex cases' do expect('(_ const ? { sym+ int+ | int+ sym+ } { str+ | regexp+ } ... )').to match_codes( '[X, :foo, :bar, 42, "a", Y]', '[42, 666, :foo, //]' ).and not_match_codes( '[42, :almost, X]', '[X, 42, :foo, 42, //]', '[X, :foo, //, :foo, X]' ) end end 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 $_) ...> ...)' } let(:captured_vals) { ['world', :hello] } it_behaves_like 'multiple capture' end context 'doubled with ellipsis' do let(:pattern) { '(array <(sym $_) ...> <(int $_) ...>)' } let(:captured_vals) { [:hello, 3] } it_behaves_like 'multiple capture' end context 'doubled with ellipsis in wrong order' do let(:pattern) { '(array <(int $_) ...> <(sym $_) ...>)' } it_behaves_like 'nonmatching' 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 sym #{symbol} ...)" } it { expect(pattern).to match_code(ruby) } 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 'regexp' do it 'matches symbols or strings' do expect('(_ _ $/abc|def|foo/i ...)').to match_codes( 'Foo(42)', 'foo(42)' ).and not_match_codes( 'bar(42)' ) 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 union subsequence in seq head' do let(:pattern) { '({foo|})' } it_behaves_like 'invalid' end context 'with unsupported subsequence in seq head within union' do let(:pattern) { '({foo bar+})' } it_behaves_like 'invalid' end context 'with variadic unions where not supported' do let(:pattern) { '(_ [_ {foo | ...}])' } 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(:ruby) { 'foo' } let(:pattern) { '(send ... ...)' } it { expect(pattern).to match_code(ruby) } # yet silly 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).to be_an_instance_of(Enumerator) 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__ + 1 } let(:call_helper) { MyClass.public_send helper_name, method_name, pattern, **keyword_defaults } let(:defined_class) do call_helper MyClass end let(:ruby) { ':hello' } let(:instance) { defined_class.new } let(:raise_argument_error) do raise_error do |err| expect(err).to be_a(ArgumentError) expect(err.message).to include('wrong number of arguments') expect(err.backtrace_locations.first.lineno).to be(line_no) if RUBY_ENGINE == 'ruby' end end context 'with a pattern without captures' do let(:pattern) { '(sym _)' } context 'def_node_matcher' do let(:helper_name) { :def_node_matcher } it 'returns the method name' do expect(call_helper).to eq method_name end 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_argument_error) end end end context 'def_node_search' do let(:helper_name) { :def_node_search } let(:ruby) { 'foo(:hello, :world)' } it 'returns the method name' do expect(call_helper).to eq method_name end context('without a predicate name') do context 'when called on matching code' do it 'returns an enumerator yielding the matches' do is_expected.to be_a(Enumerator) 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 is_expected.to be_a(Enumerator) 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_argument_error) 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_argument_error) 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_argument_error) 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 is_expected.to be_a(Enumerator) 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: Set[:hello, :foo] } } it 'returns an enumerator yielding the captures' do is_expected.to be_a(Enumerator) 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 overridden when calling the matcher' do is_expected.to be_a(Enumerator) 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 is_expected.to be_a(Enumerator) 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 is_expected.to be_a(Enumerator) 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_argument_error) 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_argument_error) 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 = Set[:hello, :foo] } 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 context 'with a pattern with a namespaced call' do let(:pattern) { '(sym #MyMod.foo)' } let(:helper_name) { :def_node_matcher } before do mod = Module.new mod::MyMod = Module.new mod::MyMod.module_eval do def self.foo(val) val == :hello end end defined_class.include mod end 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-1.24.0/spec/rubocop/ast/node_spec.rb000066400000000000000000000510331434172225000215140ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::Node do let(:ast) { parse_source(src).node } 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).not_to be_used 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).to be_used 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).to be_used expect(node.if_branch).not_to be_used expect(node.else_branch).not_to be_used 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).to be_used expect(node.body).not_to be_used end end end describe '#recursive_basic_literal?' do shared_examples 'literal' do |source| let(:src) { source } it "returns true for `#{source}`" do expect(node).to be_recursive_literal 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).not_to be_recursive_literal 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).not_to be_pure end end context 'for an integer literal' do let(:src) { '100' } it 'returns true' do expect(node).to be_pure 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).to be_pure end end context 'which contains a method call' do let(:src) { '[1, 2, 3, 3 + 4]' } it 'returns false' do expect(node).not_to be_pure 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).to be_pure end end context 'which contains a method call' do let(:src) { '{a: 1, b: 2, c: Kernel.exit}' } it 'returns false' do expect(node).not_to be_pure 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).to be_pure 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).not_to be_pure 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).not_to be_pure end end end context 'for an ivar assignment' do let(:src) { '@var = 1' } it 'returns false' do expect(node).not_to be_pure end end context 'for a gvar assignment' do let(:src) { '$var = 1' } it 'returns false' do expect(node).not_to be_pure end end context 'for a cvar assignment' do let(:src) { '@@var = 1' } it 'returns false' do expect(node).not_to be_pure end end context 'for an lvar assignment' do let(:src) { 'var = 1' } it 'returns false' do expect(node).not_to be_pure end end context 'for a class definition' do let(:src) { 'class C < Super; def method; end end' } it 'returns false' do expect(node).not_to be_pure end end context 'for a module definition' do let(:src) { 'module M; def method; end end' } it 'returns false' do expect(node).not_to be_pure 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).not_to be_pure end end context 'with no interpolation' do let(:src) { URI::DEFAULT_PARSER.make_regexp.inspect } it 'returns true' do expect(node).to be_pure end end context 'with options' do let(:opts) { 'oix' } it 'returns true' do expect(node).to be_pure 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 be_nil expect(node.left_sibling).to be_nil expect(node.right_sibling).to be_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 be_nil expect(node.right_sibling).to be_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 expect(node.arguments.children).to all be_argument_type expect(node.arguments).not_to be_argument_type 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 expect(node.arguments.children).to all be_argument_type expect(node.arguments).not_to be_argument_type 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).to be_class_constructor end end context 'module definition with a block' do let(:src) { 'Module.new { a = 42 }' } it 'matches' do expect(node).to be_class_constructor end end context 'class definition' do let(:src) { 'class Foo; a = 42; end' } it 'does not match' do expect(node.class_constructor?).to be_nil end end context 'class definition on outer scope' do let(:src) { '::Class.new { a = 42 }' } it 'matches' do expect(node).to be_class_constructor 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 be_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 context 'when using numbered parameter', :ruby27 do let(:src) do <<~RUBY Person = Struct.new(:name, :age) do do_something _1 end RUBY end it 'matches' do class_node = node.children.last expect(class_node.class_definition?).to eq(class_node.body) end 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 be_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 context 'when using numbered parameter', :ruby27 do let(:src) do <<~RUBY Person = Class.new do do_something _1 end RUBY end it 'matches' do class_node = node.children.last expect(class_node.class_definition?).to eq(class_node.body) end 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 context 'when using numbered parameter', :ruby27 do let(:src) do <<~RUBY Person = Module.new do do_something _1 end RUBY end it 'matches' do module_node = node.children.last expect(module_node.module_definition?).to eq(module_node.body) end 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 describe '#parent_module_name' do subject(:parent_module_name) { node.parent_module_name } context 'when node on top level' do let(:src) { 'def config; end' } it { is_expected.to eq 'Object' } end context 'when node on module' do let(:src) do <<~RUBY module Foo >>attr_reader :config<< end RUBY end it { is_expected.to eq 'Foo' } end context 'when node on singleton class' do let(:src) do <<~RUBY module Foo class << self >>attr_reader :config<< end end RUBY end it { is_expected.to eq 'Foo::#' } end context 'when node on class in singleton class' do let(:src) do <<~RUBY module Foo class << self class Bar >>attr_reader :config<< end end end RUBY end it { is_expected.to eq 'Foo::#::Bar' } end context 'when node nested in an unknown block' do let(:src) do <<~RUBY module Foo foo do class Bar >>attr_reader :config<< end end end RUBY end it { is_expected.to be_nil } end context 'when node nested in a class << exp' do let(:src) do <<~RUBY class A class << expr >>attr_reader :config<< end end RUBY end it { is_expected.to be_nil } end end describe '#numeric_type?' do context 'when integer literal' do let(:src) { '42' } it 'is true' do expect(node).to be_numeric_type end end context 'when float literal' do let(:src) { '42.0' } it 'is true' do expect(node).to be_numeric_type end end context 'when rational literal' do let(:src) { '42r' } it 'is true' do expect(node).to be_numeric_type end end context 'when complex literal' do let(:src) { '42i' } it 'is true' do expect(node).to be_numeric_type end end context 'when complex literal whose imaginary part is a rational' do let(:src) { '42ri' } it 'is true' do expect(node).to be_numeric_type end end context 'when string literal' do let(:src) { '"42"' } it 'is true' do expect(node).not_to be_numeric_type end end end describe '#conditional?' do context 'when `if` node' do let(:src) do <<~RUBY if condition end RUBY end it 'is true' do expect(node).to be_conditional end end context 'when `while` node' do let(:src) do <<~RUBY while condition end RUBY end it 'is true' do expect(node).to be_conditional end end context 'when `until` node' do let(:src) do <<~RUBY until condition end RUBY end it 'is true' do expect(node).to be_conditional end end context 'when `case` node' do let(:src) do <<~RUBY case condition when foo end RUBY end it 'is true' do expect(node).to be_conditional end end context 'when `case_match` node', :ruby27 do let(:src) do <<~RUBY case pattern in foo end RUBY end it 'is true' do expect(node).to be_conditional end end context 'when post condition loop node' do let(:src) do <<~RUBY begin end while condition RUBY end it 'is false' do expect(node).not_to be_conditional end end end end rubocop-ast-1.24.0/spec/rubocop/ast/op_asgn_node_spec.rb000066400000000000000000000033231434172225000232210ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::OpAsgnNode do let(:op_asgn_node) { parse_source(source).ast } describe '.new' do context 'with an `op_asgn_node` node' do let(:source) { 'var += value' } it { expect(op_asgn_node).to be_a(described_class) } end end describe '#assignment_node' do subject { op_asgn_node.assignment_node } let(:source) { 'var += value' } it { is_expected.to be_a(RuboCop::AST::AsgnNode) } end describe '#name' do subject { op_asgn_node.name } let(:source) { 'var += value' } it { is_expected.to eq(:var) } end describe '#operator' do subject { op_asgn_node.operator } context 'with +=' do let(:source) { 'var += value' } it { is_expected.to eq(:+) } end context 'with -=' do let(:source) { 'var -= value' } it { is_expected.to eq(:-) } end context 'with *=' do let(:source) { 'var *= value' } it { is_expected.to eq(:*) } end context 'with /=' do let(:source) { 'var /= value' } it { is_expected.to eq(:/) } end context 'with &=' do let(:source) { 'var &= value' } it { is_expected.to eq(:&) } end context 'with |=' do let(:source) { 'var |= value' } it { is_expected.to eq(:|) } end context 'with %=' do let(:source) { 'var %= value' } it { is_expected.to eq(:%) } end context 'with **=' do let(:source) { 'var **= value' } it { is_expected.to eq(:**) } end end describe '#expression' do include AST::Sexp subject { op_asgn_node.expression } let(:source) { 'var += value' } it { is_expected.to eq(s(:send, nil, :value)) } end end rubocop-ast-1.24.0/spec/rubocop/ast/or_asgn_node_spec.rb000066400000000000000000000013531434172225000232240ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::OrAsgnNode do let(:or_asgn_node) { parse_source(source).ast } let(:source) { 'var ||= value' } describe '.new' do it { expect(or_asgn_node).to be_a(described_class) } end describe '#assignment_node' do subject { or_asgn_node.assignment_node } it { is_expected.to be_a(RuboCop::AST::AsgnNode) } end describe '#name' do subject { or_asgn_node.name } it { is_expected.to eq(:var) } end describe '#operator' do subject { or_asgn_node.operator } it { is_expected.to eq(:'||') } end describe '#expression' do include AST::Sexp subject { or_asgn_node.expression } it { is_expected.to eq(s(:send, nil, :value)) } end end rubocop-ast-1.24.0/spec/rubocop/ast/or_node_spec.rb000066400000000000000000000055401434172225000222160ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::OrNode do subject(:or_node) { parse_source(source).ast } describe '.new' do context 'with a logical or node' do let(:source) do ':foo || :bar' end it { is_expected.to be_a(described_class) } end context 'with a semantic or node' do let(:source) do ':foo or :bar' end it { is_expected.to be_a(described_class) } end end describe '#logical_operator?' do context 'with a logical or node' do let(:source) do ':foo || :bar' end it { is_expected.to be_logical_operator } end context 'with a semantic or node' do let(:source) do ':foo or :bar' end it { is_expected.not_to be_logical_operator } end end describe '#semantic_operator?' do context 'with a logical or node' do let(:source) do ':foo || :bar' end it { is_expected.not_to be_semantic_operator } end context 'with a semantic or node' do let(:source) do ':foo or :bar' end it { is_expected.to be_semantic_operator } 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).to be_sym_type } end context 'with a semantic or node' do let(:source) do ':foo or 42' end it { expect(or_node.lhs).to be_sym_type } end end describe '#rhs' do context 'with a logical or node' do let(:source) do ':foo || 42' end it { expect(or_node.rhs).to be_int_type } end context 'with a semantic or node' do let(:source) do ':foo or 42' end it { expect(or_node.rhs).to be_int_type } end end end rubocop-ast-1.24.0/spec/rubocop/ast/pair_node_spec.rb000066400000000000000000000451521434172225000225340ustar00rootroot00000000000000# 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).to be_a(described_class) } end describe '#hash_rocket?' do context 'when using a hash rocket delimiter' do let(:source) { '{ a => 1 }' } it { expect(pair_node).to be_hash_rocket } end context 'when using a colon delimiter' do let(:source) { '{ a: 1 }' } it { expect(pair_node).not_to be_hash_rocket } end end describe '#colon?' do context 'when using a hash rocket delimiter' do let(:source) { '{ a => 1 }' } it { expect(pair_node).not_to be_colon } end context 'when using a colon delimiter' do let(:source) { '{ a: 1 }' } it { expect(pair_node).to be_colon } 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).to be_sym_type } end context 'when using a string key' do let(:source) { "{ 'a' => 1 }" } it { expect(pair_node.key).to be_str_type } end end describe '#value' do let(:source) { '{ a: 1 }' } it { expect(pair_node.value).to be_int_type } 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).to be_value_on_new_line } end context 'when value spans multiple lines' do let(:source) do ['{', ' a: (', ' )', '}'].join("\n") end it { expect(pair).not_to be_value_on_new_line } end context 'when pair is on a single line' do let(:source) { "{ 'a' => 1 }" } it { expect(pair).not_to be_value_on_new_line } 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).to be_same_line(second_pair) } end context 'when both pair is a keyword splat' do let(:source) do ['{', ' a: 1, **foo', '}'].join("\n") end it { expect(first_pair).to be_same_line(second_pair) } 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).to be_same_line(second_pair) } it { expect(second_pair).to be_same_line(first_pair) } end context 'when last pair is a keyword splat' do let(:source) do ['{', ' a: (', ' ), **foo', '}'].join("\n") end it { expect(first_pair).to be_same_line(second_pair) } it { expect(second_pair).to be_same_line(first_pair) } 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).not_to be_same_line(second_pair) } end context 'when last pair is a keyword splat' do let(:source) do ['{', ' a: 1,', ' **foo', '}'].join("\n") end it { expect(first_pair).not_to be_same_line(second_pair) } 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 describe '#value_omission?' do context 'when using hash value omission', :ruby31 do let(:source) { '{ x: }' } it { expect(pair_node).to be_value_omission } end context 'when not using hash value omission' do let(:source) { '{ x: x }' } it { expect(pair_node).not_to be_value_omission } end end end rubocop-ast-1.24.0/spec/rubocop/ast/procarg0_node_spec.rb000066400000000000000000000011221434172225000233030ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::Procarg0Node, :ruby27 do let(:procarg0_node) { parse_source(source).ast.arguments.first } describe '.new' do context 'with a block' do let(:source) { 'foo { |x| x }' } if RuboCop::AST::Builder.emit_procarg0 it { expect(procarg0_node).to be_a(described_class) } else it { expect(procarg0_node).to be_a(RuboCop::AST::ArgNode) } end end end describe '#name' do subject { procarg0_node.name } let(:source) { 'foo { |x| x }' } it { is_expected.to eq(:x) } end end rubocop-ast-1.24.0/spec/rubocop/ast/processed_source_spec.rb000066400000000000000000000352431434172225000241430ustar00rootroot00000000000000# 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 is_expected.to be_a(described_class) 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).to be_a(Parser::Source::Buffer) end end describe '#ast' do it 'is the root node of AST' do expect(processed_source.ast).to be_a(RuboCop::AST::Node) end end describe '#comments' do it 'is an array of comments' do expect(processed_source.comments).to be_a(Array) expect( processed_source.comments.first ).to be_a(Parser::Source::Comment) 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).to be_a(Array) expect(processed_source.tokens.first).to be_a(RuboCop::AST::Token) end end describe '#parser_error' do context 'when the source was properly parsed' do it 'is nil' do expect(processed_source.parser_error).to be_nil 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).to be_nil 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).to be_a(Exception) 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).to be_a(Array) 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).to be_empty expect(processed_source).to be_valid_syntax end end context 'when the source is invalid' do let(:source) { 'def invalid_code; en' } it 'returns false' do expect(processed_source).not_to be_valid_syntax 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).not_to be_empty expect(processed_source.diagnostics.first.level).to eq(:warning) expect(processed_source).to be_valid_syntax 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).not_to be_valid_syntax 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).to be_empty expect(processed_source).to be_valid_syntax 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).to be_a(Parser::Source::Comment) 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 be_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).to be_a(Enumerable) 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).to be_line_with_comment(1) expect(processed_source).to be_line_with_comment(4) end it 'returns false for lines without comments' do expect(processed_source).not_to be_line_with_comment(2) expect(processed_source).not_to be_line_with_comment(5) 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).to be_a(Parser::Source::Comment) 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).to be_a(RuboCop::AST::Token) 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 be_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) { '' } it 'returns true' do expect(processed_source).to be_blank end end context 'with source with content' do let(:source) { <<~RUBY } foo RUBY it 'returns false' do expect(processed_source).not_to be_blank end end end describe '#start_with?' do context 'with blank source' do let(:source) { '' } it 'returns false' do expect(processed_source).not_to be_start_with('start') expect(processed_source).not_to be_start_with('#') expect(processed_source).not_to be_start_with('') 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).to be_start_with('foo') expect(processed_source).to be_start_with('f') expect(processed_source).to be_start_with('') end it 'returns false when passed string that does not start source' do expect(processed_source).not_to be_start_with('bar') expect(processed_source).not_to be_start_with('qux') expect(processed_source).not_to be_start_with('1') 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-1.24.0/spec/rubocop/ast/range_node_spec.rb000066400000000000000000000017241434172225000226720ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::RangeNode do subject(:range_node) { parse_source(source).ast } describe '.new' do context 'with an inclusive range' do let(:source) do '1..2' end it { is_expected.to be_a(described_class) } it { is_expected.to be_range_type } end context 'with an exclusive range' do let(:source) do '1...2' end it { is_expected.to be_a(described_class) } it { is_expected.to be_range_type } end context 'with an infinite range' do let(:ruby_version) { 2.6 } let(:source) do '1..' end it { is_expected.to be_a(described_class) } it { is_expected.to be_range_type } end context 'with a beignless range' do let(:ruby_version) { 2.7 } let(:source) do '..42' end it { is_expected.to be_a(described_class) } it { is_expected.to be_range_type } end end end rubocop-ast-1.24.0/spec/rubocop/ast/regexp_node_spec.rb000066400000000000000000000314211434172225000230650ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::RegexpNode do subject(:regexp_node) { parse_source(source).ast } describe '.new' do let(:source) { '/re/' } it { is_expected.to be_a(described_class) } 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 context 'with a regexp with an "n" option' do let(:source) { '/abc/n' } it { expect(regexp_node.to_regexp.inspect).to eq('/abc/n') } end context 'with a regexp with an "u" option' do let(:source) { '/abc/u' } it { expect(regexp_node.to_regexp.inspect).to eq('/abc/') } end end describe '#regopt' do let(:regopt) { regexp_node.regopt } context 'with an empty regexp' do let(:source) { '//' } it { expect(regopt).to be_regopt_type } it { expect(regopt.children).to be_empty } end context 'with a regexp without option' do let(:source) { '/.+/' } it { expect(regopt).to be_regopt_type } it { expect(regopt.children).to be_empty } end context 'with a multi-line regexp without option' do let(:source) { "/\n.+\n/" } it { expect(regopt).to be_regopt_type } it { expect(regopt.children).to be_empty } end context 'with an empty regexp with option' do let(:source) { '//ix' } it { expect(regopt).to be_regopt_type } it { expect(regopt.children).to eq(%i[i x]) } end context 'with a regexp with option' do let(:source) { '/.+/imx' } it { expect(regopt).to be_regopt_type } 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).to be_regopt_type } 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 { is_expected.to be_slash_literal } end context 'with %r/-delimiters' do let(:source) { '%r/abc/' } it { is_expected.not_to be_slash_literal } end context 'with %r{-delimiters' do let(:source) { '%r{abc}' } it { is_expected.not_to be_slash_literal } end context 'with multi-line %r{-delimiters' do let(:source) do <<~SRC %r{ abc }x SRC end it { is_expected.not_to be_slash_literal } end context 'with %r<-delimiters' do let(:source) { '%rx' } it { is_expected.not_to be_slash_literal } end end describe '#percent_r_literal?' do context 'with /-delimiters' do let(:source) { '/abc/' } it { is_expected.not_to be_percent_r_literal } end context 'with %r/-delimiters' do let(:source) { '%r/abc/' } it { is_expected.to be_percent_r_literal } end context 'with %r{-delimiters' do let(:source) { '%r{abc}' } it { is_expected.to be_percent_r_literal } end context 'with multi-line %r{-delimiters' do let(:source) do <<~SRC %r{ abc }x SRC end it { is_expected.to be_percent_r_literal } end context 'with %r<-delimiters' do let(:source) { '%rx' } it { is_expected.to be_percent_r_literal } 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 { is_expected.to be_delimiter('/') } it { is_expected.not_to be_delimiter('{') } end context 'with %r/-delimiters' do let(:source) { '%r/abc/' } it { is_expected.to be_delimiter('/') } it { is_expected.not_to be_delimiter('{') } it { is_expected.not_to be_delimiter('}') } it { is_expected.not_to be_delimiter('%') } it { is_expected.not_to be_delimiter('r') } it { is_expected.not_to be_delimiter('%r') } it { is_expected.not_to be_delimiter('%r/') } end context 'with %r{-delimiters' do let(:source) { '%r{abc}' } it { is_expected.to be_delimiter('{') } it { is_expected.to be_delimiter('}') } it { is_expected.not_to be_delimiter('/') } it { is_expected.not_to be_delimiter('%') } it { is_expected.not_to be_delimiter('r') } it { is_expected.not_to be_delimiter('%r') } it { is_expected.not_to be_delimiter('%r/') } it { is_expected.not_to be_delimiter('%r{') } end context 'with multi-line %r{-delimiters' do let(:source) do <<~SRC %r{ abc }x SRC end it { is_expected.to be_delimiter('{') } it { is_expected.to be_delimiter('}') } it { is_expected.not_to be_delimiter('/') } it { is_expected.not_to be_delimiter('%') } it { is_expected.not_to be_delimiter('r') } it { is_expected.not_to be_delimiter('%r') } it { is_expected.not_to be_delimiter('%r/') } it { is_expected.not_to be_delimiter('%r{') } end context 'with %r<-delimiters' do let(:source) { '%rx' } it { is_expected.to be_delimiter('<') } it { is_expected.to be_delimiter('>') } it { is_expected.not_to be_delimiter('{') } it { is_expected.not_to be_delimiter('}') } it { is_expected.not_to be_delimiter('/') } it { is_expected.not_to be_delimiter('%') } it { is_expected.not_to be_delimiter('r') } it { is_expected.not_to be_delimiter('%r') } it { is_expected.not_to be_delimiter('%r/') } it { is_expected.not_to be_delimiter('%r{') } it { is_expected.not_to be_delimiter('%r<') } end end describe '#interpolation?' do context 'with direct variable interpoation' do let(:source) { '/\n\n#{foo}(abc)+/' } it { is_expected.to be_interpolation } end context 'with regexp quote' do let(:source) { '/\n\n#{Regexp.quote(foo)}(abc)+/' } it { is_expected.to be_interpolation } end context 'with no interpolation returns false' do let(:source) { '/a{3,6}/' } it { is_expected.not_to be_interpolation } end end describe '#multiline_mode?' do context 'with no options' do let(:source) { '/x/' } it { is_expected.not_to be_multiline_mode } end context 'with other options' do let(:source) { '/x/ix' } it { is_expected.not_to be_multiline_mode } end context 'with only m option' do let(:source) { '/x/m' } it { is_expected.to be_multiline_mode } end context 'with m and other options' do let(:source) { '/x/imx' } it { is_expected.to be_multiline_mode } end end describe '#extended?' do context 'with no options' do let(:source) { '/x/' } it { is_expected.not_to be_extended } end context 'with other options' do let(:source) { '/x/im' } it { is_expected.not_to be_extended } end context 'with only x option' do let(:source) { '/x/x' } it { is_expected.to be_extended } end context 'with x and other options' do let(:source) { '/x/ixm' } it { is_expected.to be_extended } end end describe '#ignore_case?' do context 'with no options' do let(:source) { '/x/' } it { is_expected.not_to be_ignore_case } end context 'with other options' do let(:source) { '/x/xm' } it { is_expected.not_to be_ignore_case } end context 'with only i option' do let(:source) { '/x/i' } it { is_expected.to be_ignore_case } end context 'with i and other options' do let(:source) { '/x/xim' } it { is_expected.to be_ignore_case } end end describe '#no_encoding?' do context 'with no options' do let(:source) { '/x/' } it { is_expected.not_to be_no_encoding } end context 'with other options' do let(:source) { '/x/xm' } it { is_expected.not_to be_no_encoding } end context 'with only n option' do let(:source) { '/x/n' } it { is_expected.to be_no_encoding } end context 'with n and other options' do let(:source) { '/x/xnm' } it { is_expected.to be_no_encoding } end end describe '#fixed_encoding?' do context 'with no options' do let(:source) { '/x/' } it { is_expected.not_to be_fixed_encoding } end context 'with other options' do let(:source) { '/x/xm' } it { is_expected.not_to be_fixed_encoding } end context 'with only u option' do let(:source) { '/x/u' } it { is_expected.to be_fixed_encoding } end context 'with u and other options' do let(:source) { '/x/unm' } it { is_expected.to be_fixed_encoding } end end describe '#single_interpolation?' do context 'with no options' do let(:source) { '/x/' } it { is_expected.not_to be_single_interpolation } end context 'with other options' do let(:source) { '/x/xm' } it { is_expected.not_to be_single_interpolation } end context 'with only o option' do let(:source) { '/x/o' } it { is_expected.to be_single_interpolation } end context 'with o and other options' do let(:source) { '/x/xom' } it { is_expected.to be_single_interpolation } end end end rubocop-ast-1.24.0/spec/rubocop/ast/resbody_node_spec.rb000066400000000000000000000045221434172225000232440ustar00rootroot00000000000000# 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).to be_a(described_class) } 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).to be_sym_type } 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-1.24.0/spec/rubocop/ast/rescue_node_spec.rb000066400000000000000000000053361434172225000230670ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::RescueNode do subject(: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).to be_a(described_class) } end describe '#body' do let(:source) { <<~RUBY } begin foo rescue => e end RUBY it { expect(rescue_node.body).to be_send_type } 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).to be_nil } 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).to be_send_type } 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).not_to be_else } end context 'with an else statement' do let(:source) { <<~RUBY } begin rescue FooError then foo else bar end RUBY it { expect(rescue_node).to be_else } end end end rubocop-ast-1.24.0/spec/rubocop/ast/return_node_spec.rb000066400000000000000000000002561434172225000231140ustar00rootroot00000000000000# 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-1.24.0/spec/rubocop/ast/rubocop_compatibility_spec.rb000066400000000000000000000012211434172225000251630ustar00rootroot00000000000000# 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-1.24.0/spec/rubocop/ast/self_class_node_spec.rb000066400000000000000000000017211434172225000237110ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::SelfClassNode do subject(:self_class_node) { parse_source(source).ast } describe '.new' do let(:source) do 'class << self; end' end it { is_expected.to be_a(described_class) } end describe '#identifier' do let(:source) do 'class << self; end' end it { expect(self_class_node.identifier).to be_self_type } 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).to be_send_type } end context 'with a multi-expression body' do let(:source) do 'class << self; bar; baz; end' end it { expect(self_class_node.body).to be_begin_type } 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-1.24.0/spec/rubocop/ast/send_node_spec.rb000066400000000000000000001002151434172225000225220ustar00rootroot00000000000000# 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).to be_a(described_class) } end context 'with a safe navigation method send' do let(:source) { 'foo&.bar(:baz)' } it { expect(send_node).to be_a(described_class) } end end describe '#receiver' do context 'with no receiver' do let(:source) { 'bar(:baz)' } it { expect(send_node.receiver).to be_nil } end context 'with a literal receiver' do let(:source) { "'foo'.bar(:baz)" } it { expect(send_node.receiver).to be_str_type } end context 'with a variable receiver' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.receiver).to be_send_type } 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).to be_method(:bar) } end context 'when argument is a string' do let(:source) { 'bar(:baz)' } it { expect(send_node).to be_method('bar') } end end context 'when message does not match' do context 'when argument is a symbol' do let(:source) { 'bar(:baz)' } it { expect(send_node).not_to be_method(:foo) } end context 'when argument is a string' do let(:source) { 'bar(:baz)' } it { expect(send_node).not_to be_method('foo') } 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).to be_access_modifier } 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).to be_access_modifier } 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).not_to be_bare_access_modifier } 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).to be_bare_access_modifier } end context 'when node has an argument' do let(:source) do <<~RUBY module Foo >> private :foo << end RUBY end it { expect(send_node).not_to be_bare_access_modifier } 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).not_to be_bare_access_modifier } end context 'with Ruby >= 2.7', :ruby27 do context 'when node is access modifier in block' do let(:source) do <<~RUBY included do >> module_function << end RUBY end it { expect(send_node).to be_bare_access_modifier } end context 'when node is access modifier in numblock' do let(:source) do <<~RUBY included do _1 >> module_function << end RUBY end it { expect(send_node).to be_bare_access_modifier } end 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).to be_non_bare_access_modifier } end context 'when node does not have an argument' do let(:source) do <<~RUBY module Foo >> private << end RUBY end it { expect(send_node).not_to be_non_bare_access_modifier } 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).not_to be_non_bare_access_modifier } 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).to be_macro } 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).to be_macro } 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).to be_macro } end context 'when parent is a struct constructor' do let(:source) do ['Foo = Struct.new do', '>>bar :baz<<', ' bar :qux', 'end'].join("\n") end it { expect(send_node).to be_macro } 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).to be_macro } 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).to be_macro } end context 'with Ruby >= 2.7', :ruby27 do context 'when parent is a numblock in a macro scope' do let(:source) do ['concern :Auth do', '>>bar :baz<<', ' bar _1', 'end'].join("\n") end it { expect(send_node).to be_macro } end 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).not_to be_macro } end context 'when in the global scope' do let(:source) { <<~RUBY } >>something :baz<< other RUBY it { expect(send_node).to be_macro } 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).to be_macro } end context 'without a parent' do let(:source) { 'bar :baz' } it { expect(send_node).to be_macro } 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).to be_macro } end context 'when parent is a method definition' do let(:source) do ['def foo', '>>bar :baz<<', 'end'].join("\n") end it { expect(send_node).not_to be_macro } end context 'when in an if' do let(:source) { <<~RUBY } >>bar :baz<< if qux other RUBY it { expect(send_node).to be_macro } end context 'when the condition of an if' do let(:source) { <<~RUBY } qux if >>bar :baz<< other RUBY it { expect(send_node).not_to be_macro } 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).not_to be_macro } end context 'when parent is a module' do let(:source) do ['module Foo', ' >> qux.bar :baz << ', 'end'].join("\n") end it { expect(send_node).not_to be_macro } 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).not_to be_command(:bar) } end context 'with an implicit receiver' do let(:source) { 'bar(:baz)' } it { expect(send_node).to be_command(:bar) } end end context 'when argument is a string' do context 'with an explicit receiver' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_command('bar') } end context 'with an implicit receiver' do let(:source) { 'bar(:baz)' } it { expect(send_node).to be_command('bar') } end end end describe '#arguments' do context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node.arguments).to be_empty } 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).to be_nil } end context 'with a single literal argument' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.first_argument).to be_sym_type } end context 'with a single splat argument' do let(:source) { 'foo.bar(*baz)' } it { expect(send_node.first_argument).to be_splat_type } end context 'with multiple literal arguments' do let(:source) { 'foo.bar(:baz, :qux)' } it { expect(send_node.first_argument).to be_sym_type } end context 'with multiple mixed arguments' do let(:source) { 'foo.bar(:baz, *qux)' } it { expect(send_node.first_argument).to be_sym_type } end end describe '#last_argument' do context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node.last_argument).to be_nil } end context 'with a single literal argument' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node.last_argument).to be_sym_type } end context 'with a single splat argument' do let(:source) { 'foo.bar(*baz)' } it { expect(send_node.last_argument).to be_splat_type } end context 'with multiple literal arguments' do let(:source) { 'foo.bar(:baz, :qux)' } it { expect(send_node.last_argument).to be_sym_type } end context 'with multiple mixed arguments' do let(:source) { 'foo.bar(:baz, *qux)' } it { expect(send_node.last_argument).to be_splat_type } end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_arguments } end context 'with a single literal argument' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).to be_arguments } end context 'with a single splat argument' do let(:source) { 'foo.bar(*baz)' } it { expect(send_node).to be_arguments } end context 'with multiple literal arguments' do let(:source) { 'foo.bar(:baz, :qux)' } it { expect(send_node).to be_arguments } end context 'with multiple mixed arguments' do let(:source) { 'foo.bar(:baz, *qux)' } it { expect(send_node).to be_arguments } end end describe '#parenthesized?' do context 'with no arguments' do context 'when not using parentheses' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_parenthesized } end context 'when using parentheses' do let(:source) { 'foo.bar()' } it { expect(send_node).to be_parenthesized } end end context 'with arguments' do context 'when not using parentheses' do let(:source) { 'foo.bar :baz' } it { expect(send_node).not_to be_parenthesized } end context 'when using parentheses' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).to be_parenthesized } end end end describe '#setter_method?' do context 'with a setter method' do let(:source) { 'foo.bar = :baz' } it { expect(send_node).to be_setter_method } end context 'with an indexed setter method' do let(:source) { 'foo.bar[:baz] = :qux' } it { expect(send_node).to be_setter_method } end context 'with an operator method' do let(:source) { 'foo.bar + 1' } it { expect(send_node).not_to be_setter_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_setter_method } end end describe '#operator_method?' do context 'with a binary operator method' do let(:source) { 'foo.bar + :baz' } it { expect(send_node).to be_operator_method } end context 'with a unary operator method' do let(:source) { '!foo.bar' } it { expect(send_node).to be_operator_method } end context 'with a setter method' do let(:source) { 'foo.bar = :baz' } it { expect(send_node).not_to be_operator_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_operator_method } end end describe '#nonmutating_binary_operator_method?' do context 'with a nonmutating binary operator method' do let(:source) { 'foo + bar' } it { expect(send_node).to be_nonmutating_binary_operator_method } end context 'with a mutating binary operator method' do let(:source) { 'foo << bar' } it { expect(send_node).not_to be_nonmutating_binary_operator_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_nonmutating_binary_operator_method } end end describe '#nonmutating_unary_operator_method?' do context 'with a nonmutating unary operator method' do let(:source) { '!foo' } it { expect(send_node).to be_nonmutating_unary_operator_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_nonmutating_unary_operator_method } end end describe '#nonmutating_operator_method?' do context 'with a nonmutating binary operator method' do let(:source) { 'foo + bar' } it { expect(send_node).to be_nonmutating_operator_method } end context 'with a nonmutating unary operator method' do let(:source) { '!foo' } it { expect(send_node).to be_nonmutating_operator_method } end context 'with a mutating binary operator method' do let(:source) { 'foo << bar' } it { expect(send_node).not_to be_nonmutating_operator_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_nonmutating_operator_method } end end describe '#nonmutating_array_method?' do context 'with a nonmutating Array method' do let(:source) { 'array.reverse' } it { expect(send_node).to be_nonmutating_array_method } end context 'with a mutating Array method' do let(:source) { 'array.push(foo)' } it { expect(send_node).not_to be_nonmutating_array_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_nonmutating_array_method } end end describe '#nonmutating_hash_method?' do context 'with a nonmutating Hash method' do let(:source) { 'hash.slice(:foo, :bar)' } it { expect(send_node).to be_nonmutating_hash_method } end context 'with a mutating Hash method' do let(:source) { 'hash.delete(:foo)' } it { expect(send_node).not_to be_nonmutating_hash_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_nonmutating_hash_method } end end describe '#nonmutating_string_method?' do context 'with a nonmutating String method' do let(:source) { 'string.squeeze' } it { expect(send_node).to be_nonmutating_string_method } end context 'with a mutating String method' do let(:source) { 'string.lstrip!' } it { expect(send_node).not_to be_nonmutating_string_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_nonmutating_string_method } end end describe '#comparison_method?' do context 'with a comparison method' do let(:source) { 'foo.bar >= :baz' } it { expect(send_node).to be_comparison_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_comparison_method } end context 'with a negation method' do let(:source) { '!foo' } it { expect(send_node).not_to be_comparison_method } end end describe '#assignment_method?' do context 'with an assignment method' do let(:source) { 'foo.bar = :baz' } it { expect(send_node).to be_assignment_method } end context 'with a bracket assignment method' do let(:source) { 'foo.bar[:baz] = :qux' } it { expect(send_node).to be_assignment_method } end context 'with a comparison method' do let(:source) { 'foo.bar == :qux' } it { expect(send_node).not_to be_assignment_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_assignment_method } end end describe '#enumerable_method?' do context 'with an enumerable method' do let(:source) { '>> foo.all? << { |e| bar?(e) }' } it { expect(send_node).to be_enumerable_method } end context 'with a regular method' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_enumerable_method } 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).to be_dot } end context 'without a dot' do let(:source) { 'foo + 1' } it { expect(send_node).not_to be_dot } end context 'with a double colon' do let(:source) { 'Foo::bar' } it { expect(send_node).not_to be_dot } end context 'with a unary method' do let(:source) { '!foo.bar' } it { expect(send_node).not_to be_dot } end end describe '#double_colon?' do context 'with a double colon' do let(:source) { 'Foo::bar' } it { expect(send_node).to be_double_colon } end context 'with a dot' do let(:source) { 'foo.+ 1' } it { expect(send_node).not_to be_double_colon } end context 'without a dot' do let(:source) { 'foo + 1' } it { expect(send_node).not_to be_double_colon } end context 'with a unary method' do let(:source) { '!foo.bar' } it { expect(send_node).not_to be_double_colon } end end describe '#self_receiver?' do context 'with a self receiver' do let(:source) { 'self.bar' } it { expect(send_node).to be_self_receiver } end context 'with a non-self receiver' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_self_receiver } end context 'with an implicit receiver' do let(:source) { 'bar' } it { expect(send_node).not_to be_self_receiver } end end describe '#const_receiver?' do context 'with a self receiver' do let(:source) { 'self.bar' } it { expect(send_node).not_to be_const_receiver } end context 'with a non-constant receiver' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_const_receiver } end context 'with a constant receiver' do let(:source) { 'Foo.bar' } it { expect(send_node).to be_const_receiver } end end describe '#implicit_call?' do context 'with an implicit call method' do let(:source) { 'foo.(:bar)' } it { expect(send_node).to be_implicit_call } end context 'with an explicit call method' do let(:source) { 'foo.call(:bar)' } it { expect(send_node).not_to be_implicit_call } end context 'with a regular method' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_implicit_call } end end describe '#predicate_method?' do context 'with a predicate method' do let(:source) { 'foo.bar?' } it { expect(send_node).to be_predicate_method } end context 'with a bang method' do let(:source) { 'foo.bar!' } it { expect(send_node).not_to be_predicate_method } end context 'with a regular method' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_predicate_method } end end describe '#bang_method?' do context 'with a bang method' do let(:source) { 'foo.bar!' } it { expect(send_node).to be_bang_method } end context 'with a predicate method' do let(:source) { 'foo.bar?' } it { expect(send_node).not_to be_bang_method } end context 'with a regular method' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_bang_method } end end describe '#camel_case_method?' do context 'with a camel case method' do let(:source) { 'Integer(1.0)' } it { expect(send_node).to be_camel_case_method } end context 'with a regular method' do let(:source) { 'integer(1.0)' } it { expect(send_node).not_to be_camel_case_method } end end describe '#block_argument?' do context 'with a block argument' do let(:source) { 'foo.bar(&baz)' } it { expect(send_node).to be_block_argument } end context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_block_argument } end context 'with regular arguments' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_block_argument } end context 'with mixed arguments' do let(:source) { 'foo.bar(:baz, &qux)' } it { expect(send_node).to be_block_argument } end end describe '#block_literal?' do context 'with a block literal' do let(:source) { '>> foo.bar << { |q| baz(q) }' } it { expect(send_node).to be_block_literal } end context 'with a block argument' do let(:source) { 'foo.bar(&baz)' } it { expect(send_node).not_to be_block_literal } end context 'with no block' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_block_literal } end context 'with Ruby >= 2.7', :ruby27 do context 'with a numblock literal' do let(:source) { '>> foo.bar << { baz(_1) }' } it { expect(send_node).to be_block_literal } end end end describe '#arithmetic_operation?' do context 'with a binary arithmetic operation' do let(:source) { 'foo + bar' } it { expect(send_node).to be_arithmetic_operation } end context 'with a unary numeric operation' do let(:source) { '+foo' } it { expect(send_node).not_to be_arithmetic_operation } end context 'with a regular method call' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_arithmetic_operation } 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).to be_block_type } end context 'with a block argument' do let(:source) { 'foo.bar(&baz)' } it { expect(send_node.block_node).to be_nil } end context 'with no block' do let(:source) { 'foo.bar' } it { expect(send_node.block_node).to be_nil } end context 'with Ruby >= 2.7', :ruby27 do context 'with a numblock literal' do let(:source) { '>>foo.bar<< { baz(_1) }' } it { expect(send_node.block_node).to be_numblock_type } end end end describe '#splat_argument?' do context 'with a splat argument' do let(:source) { 'foo.bar(*baz)' } it { expect(send_node).to be_splat_argument } end context 'with no arguments' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_splat_argument } end context 'with regular arguments' do let(:source) { 'foo.bar(:baz)' } it { expect(send_node).not_to be_splat_argument } end context 'with mixed arguments' do let(:source) { 'foo.bar(:baz, *qux)' } it { expect(send_node).to be_splat_argument } end end describe '#def_modifier?' do context 'with a prefixed def modifier' do let(:source) { 'foo def bar; end' } it { expect(send_node).to be_def_modifier } end context 'with several prefixed def modifiers' do let(:source) { 'foo bar baz def qux; end' } it { expect(send_node).to be_def_modifier } end context 'with a block containing a method definition' do let(:source) { 'foo bar { baz def qux; end }' } it { expect(send_node).not_to be_def_modifier } 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.method_name).to eq(:bar) } end context 'with several prefixed def modifiers' do let(:source) { 'foo bar baz def qux; end' } it { expect(send_node.def_modifier.method_name).to eq(:qux) } end context 'with a block containing a method definition' do let(:source) { 'foo bar { baz def qux; end }' } it { expect(send_node.def_modifier).to be_nil } end context 'with call with no argument' do let(:source) { 'foo' } it { expect(send_node.def_modifier).to be_nil } end end describe '#negation_method?' do context 'with prefix `not`' do let(:source) { 'not foo' } it { expect(send_node).to be_negation_method } end context 'with suffix `not`' do let(:source) { 'foo.not' } it { expect(send_node).not_to be_negation_method } end context 'with prefix bang' do let(:source) { '!foo' } it { expect(send_node).to be_negation_method } end context 'with a non-negated method' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_negation_method } end end describe '#prefix_not?' do context 'with keyword `not`' do let(:source) { 'not foo' } it { expect(send_node).to be_prefix_not } end context 'with a bang method' do let(:source) { '!foo' } it { expect(send_node).not_to be_prefix_not } end context 'with a non-negated method' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_prefix_not } end end describe '#prefix_bang?' do context 'with keyword `not`' do let(:source) { 'not foo' } it { expect(send_node).not_to be_prefix_bang } end context 'with a bang method' do let(:source) { '!foo' } it { expect(send_node).to be_prefix_bang } end context 'with a non-negated method' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_prefix_bang } end end describe '#lambda?' do context 'with a lambda method' do let(:source) { '>> lambda << { |foo| bar(foo) }' } it { expect(send_node).to be_lambda } end context 'with a method named lambda in a class' do let(:source) { '>> foo.lambda << { |bar| baz }' } it { expect(send_node).not_to be_lambda } end context 'with a stabby lambda method' do let(:source) { '>> -> << (foo) { do_something(foo) }' } it { expect(send_node).to be_lambda } end context 'with a non-lambda method' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_lambda } end end describe '#lambda_literal?' do context 'with a stabby lambda' do let(:source) { '>> -> << (foo) { do_something(foo) }' } it { expect(send_node).to be_lambda_literal } end context 'with a lambda method' do let(:source) { '>> lambda << { |foo| bar(foo) }' } it { expect(send_node).not_to be_lambda_literal } end context 'with a non-lambda method' do let(:source) { 'foo.bar' } it { expect(send_node).not_to be_lambda } end # Regression test https://github.com/rubocop/rubocop/pull/5194 context 'with `a.() {}` style method' do let(:source) { '>>a.()<< {}' } it { expect(send_node).not_to be_lambda } end end describe '#unary_operation?' do context 'with a unary operation' do let(:source) { '-foo' } it { expect(send_node).to be_unary_operation } end context 'with a binary operation' do let(:source) { 'foo + bar' } it { expect(send_node).not_to be_unary_operation } end context 'with a regular method call' do let(:source) { 'foo(bar)' } it { expect(send_node).not_to be_unary_operation } end context 'with an implicit call method' do let(:source) { 'foo.(:baz)' } it { expect(send_node).not_to be_unary_operation } end end describe '#binary_operation??' do context 'with a unary operation' do let(:source) { '-foo' } it { expect(send_node).not_to be_binary_operation } end context 'with a binary operation' do let(:source) { 'foo + bar' } it { expect(send_node).to be_binary_operation } end context 'with a regular method call' do let(:source) { 'foo(bar)' } it { expect(send_node).not_to be_binary_operation } end context 'with an implicit call method' do let(:source) { 'foo.(:baz)' } it { expect(send_node).not_to be_binary_operation } end end describe '#post_condition_loop?' do let(:source) { 'foo(bar)' } it { expect(send_node).not_to be_post_condition_loop } end describe '#loop_keyword?' do let(:source) { 'foo(bar)' } it { expect(send_node).not_to be_loop_keyword } end end rubocop-ast-1.24.0/spec/rubocop/ast/str_node_spec.rb000066400000000000000000000032111434172225000223770ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::StrNode do subject(:str_node) { parse_source(source).ast } describe '.new' do context 'with a normal string' do let(:source) { "'foo'" } it { is_expected.to be_a(described_class) } end context 'with a string with interpolation' do let(:source) { '"#{foo}"' } it { is_expected.to be_a(described_class) } end context 'with a heredoc' do let(:source) do <<~RUBY <<-CODE foo bar CODE RUBY end it { is_expected.to be_a(described_class) } end end describe '#character_literal?' do context 'with a character literal' do let(:source) { '?\n' } it { is_expected.to be_character_literal } end context 'with a normal string literal' do let(:source) { '"\n"' } it { is_expected.not_to be_character_literal } end context 'with a heredoc' do let(:source) do <<~RUBY <<-CODE foo bar CODE RUBY end it { is_expected.not_to be_character_literal } end end describe '#heredoc?' do context 'with a normal string' do let(:source) { "'foo'" } it { is_expected.not_to be_heredoc } end context 'with a string with interpolation' do let(:source) { '"#{foo}"' } it { is_expected.not_to be_heredoc } end context 'with a heredoc' do let(:source) do <<~RUBY <<-CODE foo bar CODE RUBY end it { is_expected.to be_heredoc } end end end rubocop-ast-1.24.0/spec/rubocop/ast/super_node_spec.rb000066400000000000000000000221211434172225000227260ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::SuperNode do subject(:super_node) { ast } let(:ast) { parse_source(source).ast } describe '.new' do context 'with a super node' do let(:source) { 'super(:baz)' } it { is_expected.to be_a(described_class) } end context 'with a zsuper node' do let(:source) { 'super' } it { is_expected.to be_a(described_class) } end end describe '#receiver' do let(:source) { 'super(foo)' } it { expect(super_node.receiver).to be_nil } 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 { is_expected.to be_method(:super) } end context 'when argument is a string' do let(:source) { 'super(:baz)' } it { is_expected.to be_method('super') } end end context 'when message does not match' do context 'when argument is a symbol' do let(:source) { 'super(:baz)' } it { is_expected.not_to be_method(:foo) } end context 'when argument is a string' do let(:source) { 'super(:baz)' } it { is_expected.not_to be_method('foo') } end end end describe '#macro?' do subject(:super_node) { ast.children[2] } let(:source) do ['def initialize', ' super(foo)', 'end'].join("\n") end it { is_expected.not_to be_macro } end describe '#command?' do context 'when argument is a symbol' do let(:source) { 'super(foo)' } it { is_expected.to be_command(:super) } end context 'when argument is a string' do let(:source) { 'super(foo)' } it { is_expected.to be_command('super') } end end describe '#setter_method?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_setter_method } end describe '#operator_method?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_operator_method } end describe '#comparison_method?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_comparison_method } end describe '#assignment_method?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_assignment_method } end describe '#dot?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_dot } end describe '#double_colon?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_double_colon } end describe '#self_receiver?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_self_receiver } end describe '#const_receiver?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_const_receiver } end describe '#implicit_call?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_implicit_call } end describe '#predicate_method?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_predicate_method } end describe '#bang_method?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_bang_method } end describe '#camel_case_method?' do let(:source) { 'super(foo)' } it { is_expected.not_to be_camel_case_method } end describe '#parenthesized?' do context 'with no arguments' do context 'when not using parentheses' do let(:source) { 'super' } it { is_expected.not_to be_parenthesized } end context 'when using parentheses' do let(:source) { 'foo.bar()' } it { is_expected.to be_parenthesized } end end context 'with arguments' do context 'when not using parentheses' do let(:source) { 'foo.bar :baz' } it { is_expected.not_to be_parenthesized } end context 'when using parentheses' do let(:source) { 'foo.bar(:baz)' } it { is_expected.to be_parenthesized } end end end describe '#block_argument?' do context 'with a block argument' do let(:source) { 'super(&baz)' } it { is_expected.to be_block_argument } end context 'with no arguments' do let(:source) { 'super' } it { is_expected.not_to be_block_argument } end context 'with regular arguments' do let(:source) { 'super(:baz)' } it { is_expected.not_to be_block_argument } end context 'with mixed arguments' do let(:source) { 'super(:baz, &qux)' } it { is_expected.to be_block_argument } end end describe '#block_literal?' do context 'with a block literal' do subject(:super_node) { ast.children[0] } let(:source) { 'super { |q| baz(q) }' } it { is_expected.to be_block_literal } end context 'with a block argument' do let(:source) { 'super(&baz)' } it { is_expected.not_to be_block_literal } end context 'with no block' do let(:source) { 'super' } it { is_expected.not_to be_block_literal } end end describe '#block_node' do context 'with a block literal' do subject(:super_node) { ast.children[0] } let(:source) { 'super { |q| baz(q) }' } it { expect(super_node.block_node).to be_block_type } end context 'with a block argument' do let(:source) { 'super(&baz)' } it { expect(super_node.block_node).to be_nil } end context 'with no block' do let(:source) { 'super' } it { expect(super_node.block_node).to be_nil } end end describe '#arguments' do context 'with no arguments' do let(:source) { 'super' } it { expect(super_node.arguments).to be_empty } 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).to be_nil } end context 'with a single literal argument' do let(:source) { 'super(:baz)' } it { expect(super_node.first_argument).to be_sym_type } end context 'with a single splat argument' do let(:source) { 'super(*baz)' } it { expect(super_node.first_argument).to be_splat_type } end context 'with multiple literal arguments' do let(:source) { 'super(:baz, :qux)' } it { expect(super_node.first_argument).to be_sym_type } end context 'with multiple mixed arguments' do let(:source) { 'superr(:baz, *qux)' } it { expect(super_node.first_argument).to be_sym_type } end end describe '#last_argument' do context 'with no arguments' do let(:source) { 'super' } it { expect(super_node.last_argument).to be_nil } end context 'with a single literal argument' do let(:source) { 'super(:baz)' } it { expect(super_node.last_argument).to be_sym_type } end context 'with a single splat argument' do let(:source) { 'super(*baz)' } it { expect(super_node.last_argument).to be_splat_type } end context 'with multiple literal arguments' do let(:source) { 'super(:baz, :qux)' } it { expect(super_node.last_argument).to be_sym_type } end context 'with multiple mixed arguments' do let(:source) { 'super(:baz, *qux)' } it { expect(super_node.last_argument).to be_splat_type } end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'super' } it { is_expected.not_to be_arguments } end context 'with a single literal argument' do let(:source) { 'super(:baz)' } it { is_expected.to be_arguments } end context 'with a single splat argument' do let(:source) { 'super(*baz)' } it { is_expected.to be_arguments } end context 'with multiple literal arguments' do let(:source) { 'super(:baz, :qux)' } it { is_expected.to be_arguments } end context 'with multiple mixed arguments' do let(:source) { 'super(:baz, *qux)' } it { is_expected.to be_arguments } end end describe '#splat_argument?' do context 'with a splat argument' do let(:source) { 'super(*baz)' } it { is_expected.to be_splat_argument } end context 'with no arguments' do let(:source) { 'super' } it { is_expected.not_to be_splat_argument } end context 'with regular arguments' do let(:source) { 'super(:baz)' } it { is_expected.not_to be_splat_argument } end context 'with mixed arguments' do let(:source) { 'super(:baz, *qux)' } it { is_expected.to be_splat_argument } end end end rubocop-ast-1.24.0/spec/rubocop/ast/symbol_node_spec.rb000066400000000000000000000006401434172225000230770ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::SymbolNode do subject(:sym_node) { parse_source(source).ast } describe '.new' do context 'with a symbol node' do let(:source) do ':foo' end it { is_expected.to be_a(described_class) } end end describe '#value' do let(:source) do ':foo' end it { expect(sym_node.value).to eq(:foo) } end end rubocop-ast-1.24.0/spec/rubocop/ast/token_spec.rb000066400000000000000000000304031434172225000217050ustar00rootroot00000000000000# 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.to_i 1..42 1...42 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(:irange_token) { processed_source.find_token { |t| t.text == '..' } } let(:erange_token) { processed_source.find_token { |t| t.text == '...' } } let(:dot_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' } } let(:new_line_token) { processed_source.find_token { |t| t.line == 7 && t.column == 3 } } 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 7 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 73 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 76 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?).to be_a(MatchData) expect(right_ref_bracket_token.space_after?).to be_a(MatchData) expect(left_array_bracket_token).to be_space_after expect(right_ref_bracket_token).to be_space_after 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?).to be_a(MatchData) expect(equals_token.space_before?).to be_a(MatchData) expect(left_array_bracket_token).to be_space_before expect(equals_token).to be_space_before 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).to be_comment end it 'returns false for non comment tokens' do expect(zero_token).not_to be_comment expect(semicolon_token).not_to be_comment end end describe '#semicolon?' do it 'returns true for semicolon tokens' do expect(semicolon_token).to be_semicolon end it 'returns false for non semicolon tokens' do expect(comment_token).not_to be_semicolon expect(comma_token).not_to be_semicolon end end describe '#left_array_bracket?' do it 'returns true for left_array_bracket tokens' do expect(left_array_bracket_token).to be_left_array_bracket end it 'returns false for non left_array_bracket tokens' do expect(left_ref_bracket_token).not_to be_left_array_bracket expect(right_array_bracket_token).not_to be_left_array_bracket end end describe '#left_ref_bracket?' do it 'returns true for left_ref_bracket tokens' do expect(left_ref_bracket_token).to be_left_ref_bracket end it 'returns false for non left_ref_bracket tokens' do expect(left_array_bracket_token).not_to be_left_ref_bracket expect(right_ref_bracket_token).not_to be_left_ref_bracket end end describe '#left_bracket?' do it 'returns true for all left_bracket tokens' do expect(left_ref_bracket_token).to be_left_bracket expect(left_array_bracket_token).to be_left_bracket end it 'returns false for non left_bracket tokens' do expect(right_ref_bracket_token).not_to be_left_bracket expect(right_array_bracket_token).not_to be_left_bracket end end describe '#right_bracket?' do it 'returns true for all right_bracket tokens' do expect(right_ref_bracket_token).to be_right_bracket expect(right_array_bracket_token).to be_right_bracket end it 'returns false for non right_bracket tokens' do expect(left_ref_bracket_token).not_to be_right_bracket expect(left_array_bracket_token).not_to be_right_bracket end end describe '#left_brace?' do it 'returns true for right_bracket tokens' do expect(right_ref_bracket_token).to be_right_bracket expect(right_array_bracket_token).to be_right_bracket end it 'returns false for non right_bracket tokens' do expect(left_ref_bracket_token).not_to be_right_bracket expect(left_array_bracket_token).not_to be_right_bracket end end describe '#comma?' do it 'returns true for comma tokens' do expect(comma_token).to be_comma end it 'returns false for non comma tokens' do expect(semicolon_token).not_to be_comma expect(right_ref_bracket_token).not_to be_comma end end describe '#dot?' do it 'returns true for dot tokens' do expect(dot_token).to be_dot end it 'returns false for non dot tokens' do expect(semicolon_token).not_to be_dot expect(right_ref_bracket_token).not_to be_dot end end describe '#regexp_dots?' do it 'returns true for regexp tokens' do expect(irange_token).to be_regexp_dots expect(erange_token).to be_regexp_dots end it 'returns false for non comma tokens' do expect(semicolon_token).not_to be_regexp_dots expect(right_ref_bracket_token).not_to be_regexp_dots 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).to be_rescue_modifier end it 'returns false for non rescue modifier tokens' do expect(first_token).not_to be_rescue_modifier expect(end_token).not_to be_rescue_modifier end end describe '#end?' do it 'returns true for end tokens' do expect(end_token).to be_end end it 'returns false for non end tokens' do expect(semicolon_token).not_to be_end expect(comment_token).not_to be_end end end describe '#equals_sign?' do it 'returns true for equals sign tokens' do expect(equals_token).to be_equal_sign end it 'returns false for non equals sign tokens' do expect(semicolon_token).not_to be_equal_sign expect(comma_token).not_to be_equal_sign end end describe '#new_line?' do it 'returns true for new line tokens' do expect(new_line_token).to be_a_new_line end it 'returns false for non new line tokens' do expect(end_token).not_to be_a_new_line expect(semicolon_token).not_to be_a_new_line 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).to be_left_brace end it 'returns false for non left hash brace tokens' do expect(left_block_brace_token).not_to be_left_brace expect(right_hash_brace_token).not_to be_left_brace end end describe '#left_curly_brace?' do it 'returns true for left block brace tokens' do expect(left_block_brace_token).to be_left_curly_brace end it 'returns false for non left block brace tokens' do expect(left_hash_brace_token).not_to be_left_curly_brace expect(right_block_brace_token).not_to be_left_curly_brace end end describe '#right_curly_brace?' do it 'returns true for all right brace tokens' do expect(right_hash_brace_token).to be_right_curly_brace expect(right_block_brace_token).to be_right_curly_brace end it 'returns false for non right brace tokens' do expect(left_hash_brace_token).not_to be_right_curly_brace expect(left_parens_token).not_to be_right_curly_brace end end describe '#left_parens?' do it 'returns true for left parens tokens' do expect(left_parens_token).to be_left_parens end it 'returns false for non left parens tokens' do expect(left_hash_brace_token).not_to be_left_parens expect(right_parens_token).not_to be_left_parens end end describe '#right_parens?' do it 'returns true for right parens tokens' do expect(right_parens_token).to be_right_parens end it 'returns false for non right parens tokens' do expect(right_hash_brace_token).not_to be_right_parens expect(left_parens_token).not_to be_right_parens end end end end end rubocop-ast-1.24.0/spec/rubocop/ast/traversal_spec.rb000066400000000000000000000052721434172225000225760ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::Traversal do subject(:traverse) do instance.walk(node) instance end let(:ast) { parse_source(source).ast } let(:instance) { klass.new } let(:node) { ast } context 'when a class defines on_arg', :ruby30 do let(:klass) do Class.new do attr_reader :calls include RuboCop::AST::Traversal def on_arg(node) (@calls ||= []) << node.children.first super end end end let(:source) { <<~RUBY } class Foo def example 42.times { |x| p x } end end RUBY it 'calls it for all arguments', :ruby30 do expect(traverse.calls).to eq %i[x] end end context 'when a class defines `on_block_pass`', :ruby31 do let(:klass) do Class.new do attr_reader :calls include RuboCop::AST::Traversal def on_block_pass(node) (@calls ||= []) << node.children.first&.type super end end end let(:source) { <<~RUBY } def foo(&) # Anonymous block forwarding. bar(&) end def baz(&block) qux(&block) end RUBY it 'calls it for all block-pass arguments' do expect(traverse.calls).to eq [nil, :lvar] end end File.read("#{__dir__}/fixtures/code_examples.rb") .split("#----\n") .each_with_index do |example, _i| context "for example #{example}", :ruby27 do let(:klass) do Struct.new(:hits) do include RuboCop::AST::Traversal def initialize super(0) end instance_methods.grep(/^on_/).each do |m| define_method(m) do |node| self.hits += 1 super(node) end end end end let(:source) { "foo=bar=baz=nil; #{example}" } it 'traverses all nodes' do actual = node.each_node.count expect(traverse.hits).to eql(actual) end end end it 'knows all current node types' do expect(RuboCop::AST::Traversal::MISSING).to eq [] end # Sanity checking the debugging checks context 'when given an unexpected AST' do include RuboCop::AST::Sexp let(:klass) { Class.new { include RuboCop::AST::Traversal } } context 'with too few children' do let(:node) { s(:int) } it 'raises debugging error' do expect { traverse }.to raise_error(RuboCop::AST::Traversal::DebugError) end end context 'with too many children' do let(:node) { s(:int, 1, 2) } it 'raises debugging error' do expect { traverse }.to raise_error(RuboCop::AST::Traversal::DebugError) end end end end rubocop-ast-1.24.0/spec/rubocop/ast/until_node_spec.rb000066400000000000000000000032101434172225000227210ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::UntilNode do subject(:until_node) { parse_source(source).ast } describe '.new' do context 'with a statement until' do let(:source) { 'until foo; bar; end' } it { is_expected.to be_a(described_class) } end context 'with a modifier until' do let(:source) { 'begin foo; end until bar' } it { is_expected.to be_a(described_class) } 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 { is_expected.to be_do } end context 'without a do keyword' do let(:source) { 'until foo; bar; end' } it { is_expected.not_to be_do } end end describe '#post_condition_loop?' do context 'with a statement until' do let(:source) { 'until foo; bar; end' } it { is_expected.not_to be_post_condition_loop } end context 'with a modifier until' do let(:source) { 'begin foo; end until bar' } it { is_expected.to be_post_condition_loop } end end describe '#loop_keyword?' do context 'with a statement until' do let(:source) { 'until foo; bar; end' } it { is_expected.to be_loop_keyword } end context 'with a modifier until' do let(:source) { 'begin foo; end until bar' } it { is_expected.to be_loop_keyword } end end end rubocop-ast-1.24.0/spec/rubocop/ast/when_node_spec.rb000066400000000000000000000051741434172225000225420ustar00rootroot00000000000000# 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).to be_a(described_class) } 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).to be_a(Enumerator) } 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).to be_then } end context 'without a then keyword' do let(:source) do ['case', 'when :foo', ' bar', 'end'].join("\n") end it { expect(when_node).not_to be_then } 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).to be_sym_type } end context 'without a then keyword' do let(:source) do ['case', 'when :foo', ' [:bar, :baz]', 'end'].join("\n") end it { expect(when_node.body).to be_array_type } 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-1.24.0/spec/rubocop/ast/while_node_spec.rb000066400000000000000000000032101434172225000226760ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::WhileNode do subject(:while_node) { parse_source(source).ast } describe '.new' do context 'with a statement while' do let(:source) { 'while foo; bar; end' } it { is_expected.to be_a(described_class) } end context 'with a modifier while' do let(:source) { 'begin foo; end while bar' } it { is_expected.to be_a(described_class) } 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 { is_expected.to be_do } end context 'without a do keyword' do let(:source) { 'while foo; bar; end' } it { is_expected.not_to be_do } end end describe '#post_condition_loop?' do context 'with a statement while' do let(:source) { 'while foo; bar; end' } it { is_expected.not_to be_post_condition_loop } end context 'with a modifier while' do let(:source) { 'begin foo; end while bar' } it { is_expected.to be_post_condition_loop } end end describe '#loop_keyword?' do context 'with a statement while' do let(:source) { 'while foo; bar; end' } it { is_expected.to be_loop_keyword } end context 'with a modifier while' do let(:source) { 'begin foo; end while bar' } it { is_expected.to be_loop_keyword } end end end rubocop-ast-1.24.0/spec/rubocop/ast/wrapped_arguments_node.rb000066400000000000000000000025341434172225000243130ustar00rootroot00000000000000# frozen_string_literal: true RSpec.shared_examples 'wrapped arguments node' do |keyword| subject(:return_node) { parse_source(source).ast } describe '.new' do context 'without arguments' do let(:source) { keyword } it { is_expected.to be_a(described_class) } end context 'with arguments' do let(:source) { "#{keyword} :foo" } it { is_expected.to be_a(described_class) } end end describe '#arguments' do context 'with no arguments' do let(:source) { keyword } it { expect(return_node.arguments).to be_empty } end context 'with no arguments and braces' do let(:source) { "#{keyword}()" } it { expect(return_node.arguments).to be_empty } 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-1.24.0/spec/rubocop/ast/yield_node_spec.rb000066400000000000000000000173641434172225000227130ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe RuboCop::AST::YieldNode do subject(:yield_node) { ast } let(:ast) { parse_source(source).ast } describe '.new' do let(:source) { 'yield :foo, :bar' } it { is_expected.to be_a(described_class) } end describe '#receiver' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.receiver).to be_nil } 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 { is_expected.to be_method(:yield) } end context 'when argument is a string' do let(:source) { 'yield :foo' } it { is_expected.to be_method('yield') } end end context 'when message does not match' do context 'when argument is a symbol' do let(:source) { 'yield :bar' } it { is_expected.not_to be_method(:foo) } end context 'when argument is a string' do let(:source) { 'yield :bar' } it { is_expected.not_to be_method('foo') } end end end describe '#macro?' do subject(:yield_node) { ast.children[2] } let(:source) do ['def give_me_bar', ' yield :bar', 'end'].join("\n") end it { is_expected.not_to be_macro } end describe '#command?' do context 'when argument is a symbol' do let(:source) { 'yield :bar' } it { is_expected.to be_command(:yield) } end context 'when argument is a string' do let(:source) { 'yield :bar' } it { is_expected.to be_command('yield') } end end describe '#arguments' do context 'with no arguments' do let(:source) { 'yield' } it { expect(yield_node.arguments).to be_empty } 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).to be_nil } end context 'with a single literal argument' do let(:source) { 'yield :foo' } it { expect(yield_node.first_argument).to be_sym_type } end context 'with a single splat argument' do let(:source) { 'yield *foo' } it { expect(yield_node.first_argument).to be_splat_type } end context 'with multiple literal arguments' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.first_argument).to be_sym_type } end context 'with multiple mixed arguments' do let(:source) { 'yield :foo, *bar' } it { expect(yield_node.first_argument).to be_sym_type } end end describe '#last_argument' do context 'with no arguments' do let(:source) { 'yield' } it { expect(yield_node.last_argument).to be_nil } end context 'with a single literal argument' do let(:source) { 'yield :foo' } it { expect(yield_node.last_argument).to be_sym_type } end context 'with a single splat argument' do let(:source) { 'yield *foo' } it { expect(yield_node.last_argument).to be_splat_type } end context 'with multiple literal arguments' do let(:source) { 'yield :foo, :bar' } it { expect(yield_node.last_argument).to be_sym_type } end context 'with multiple mixed arguments' do let(:source) { 'yield :foo, *bar' } it { expect(yield_node.last_argument).to be_splat_type } end end describe '#arguments?' do context 'with no arguments' do let(:source) { 'yield' } it { is_expected.not_to be_arguments } end context 'with a single literal argument' do let(:source) { 'yield :foo' } it { is_expected.to be_arguments } end context 'with a single splat argument' do let(:source) { 'yield *foo' } it { is_expected.to be_arguments } end context 'with multiple literal arguments' do let(:source) { 'yield :foo, :bar' } it { is_expected.to be_arguments } end context 'with multiple mixed arguments' do let(:source) { 'yield :foo, *bar' } it { is_expected.to be_arguments } end end describe '#parenthesized?' do context 'with no arguments' do context 'when not using parentheses' do let(:source) { 'yield' } it { is_expected.not_to be_parenthesized } end context 'when using parentheses' do let(:source) { 'yield()' } it { is_expected.to be_parenthesized } end end context 'with arguments' do context 'when not using parentheses' do let(:source) { 'yield :foo' } it { is_expected.not_to be_parenthesized } end context 'when using parentheses' do let(:source) { 'yield(:foo)' } it { is_expected.to be_parenthesized } end end end describe '#setter_method?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_setter_method } end describe '#operator_method?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_operator_method } end describe '#comparison_method?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_comparison_method } end describe '#assignment_method?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_assignment_method } end describe '#dot?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_dot } end describe '#double_colon?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_double_colon } end describe '#self_receiver?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_self_receiver } end describe '#const_receiver?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_const_receiver } end describe '#implicit_call?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_implicit_call } end describe '#predicate_method?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_predicate_method } end describe '#bang_method?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_bang_method } end describe '#camel_case_method?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_camel_case_method } end describe '#block_argument?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_block_argument } end describe '#block_literal?' do let(:source) { 'yield :foo' } it { is_expected.not_to be_block_literal } end describe '#block_node' do let(:source) { 'yield :foo' } it { expect(yield_node.block_node).to be_nil } end describe '#splat_argument?' do context 'with a splat argument' do let(:source) { 'yield *foo' } it { is_expected.to be_splat_argument } end context 'with no arguments' do let(:source) { 'yield' } it { is_expected.not_to be_splat_argument } end context 'with regular arguments' do let(:source) { 'yield :foo' } it { is_expected.not_to be_splat_argument } end context 'with mixed arguments' do let(:source) { 'yield :foo, *bar' } it { is_expected.to be_splat_argument } end end end rubocop-ast-1.24.0/spec/spec_helper.rb000066400000000000000000000047131434172225000176110ustar00rootroot00000000000000# frozen_string_literal: true require 'yaml' $VERBOSE = true if ENV.fetch('COVERAGE', 'f').start_with? 't' require 'simplecov' SimpleCov.start end ENV['RUBOCOP_DEBUG'] = 't' require 'rubocop-ast' if ENV['MODERNIZE'] RuboCop::AST::Builder.modernize RuboCop::AST::Builder.emit_forward_arg = false # inverse of default if RuboCop::AST::Builder.respond_to?(:emit_match_pattern=) RuboCop::AST::Builder.emit_match_pattern = false # inverse of default end 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 RSpec.shared_context 'ruby 3.0', :ruby30 do let(:ruby_version) { 3.0 } end RSpec.shared_context 'ruby 3.1', :ruby31 do let(:ruby_version) { 3.1 } end RSpec.shared_context 'ruby 3.2', :ruby32 do let(:ruby_version) { 3.2 } 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-1.24.0/spec/tasks/000077500000000000000000000000001434172225000161135ustar00rootroot00000000000000rubocop-ast-1.24.0/spec/tasks/changelog_spec.rb000066400000000000000000000053421434172225000214050ustar00rootroot00000000000000# frozen_string_literal: true return unless RUBY_VERSION >= '2.6' require_relative '../../tasks/changelog' # rubocop:disable RSpec/ExampleLength RSpec.describe Changelog do subject(:changelog) do list = entries.to_h { |e| [e.path, e.content] } described_class.new(content: <<~CHANGELOG, entries: list) # Change log ## master (unreleased) ### New features * [#bogus] Bogus feature * [#bogus] Other bogus feature ## 0.7.1 (2020-09-28) ### Bug fixes * [#127](https://github.com/rubocop/rubocop-ast/pull/127): Fix dependency issue for JRuby. ([@marcandre][]) ## 0.7.0 (2020-09-27) ### New features * [#105](https://github.com/rubocop/rubocop-ast/pull/105): `NodePattern` stuff... * [#109](https://github.com/rubocop/rubocop-ast/pull/109): Add `NodePattern` debugging rake tasks: `test_pattern`, `compile`, `parse`. See also [this app](https://nodepattern.herokuapp.com) ([@marcandre][]) * [#110](https://github.com/rubocop/rubocop-ast/pull/110): Add `NodePattern` support for multiple terms unions. ([@marcandre][]) * [#111](https://github.com/rubocop/rubocop-ast/pull/111): Optimize some `NodePattern`s by using `Set`s. ([@marcandre][]) * [#112](https://github.com/rubocop/rubocop-ast/pull/112): Add `NodePattern` support for Regexp literals. ([@marcandre][]) more stuf.... [@marcandre]: https://github.com/marcandre [@johndoexx]: https://github.com/johndoexx CHANGELOG end let(:entries) do %i[fix new fix].map.with_index do |type, i| Changelog::Entry.new(type: type, body: "Do something cool#{'x' * i}", user: "johndoe#{'x' * i}") end end let(:entry) { entries.first } describe Changelog::Entry do it 'generates correct content' do expect(entry.content).to eq <<~MD * [#x](https://github.com/rubocop/rubocop-ast/pull/x): Do something cool. ([@johndoe][]) MD end end it 'parses correctly' do expect(changelog.rest).to start_with('## 0.7.1 (2020-09-28)') end it 'merges correctly' do expect(changelog.unreleased_content).to eq(<<~CHANGELOG) ### New features * [#bogus] Bogus feature * [#bogus] Other bogus feature * [#x](https://github.com/rubocop/rubocop-ast/pull/x): Do something coolx. ([@johndoex][]) ### Bug fixes * [#x](https://github.com/rubocop/rubocop-ast/pull/x): Do something cool. ([@johndoe][]) * [#x](https://github.com/rubocop/rubocop-ast/pull/x): Do something coolxx. ([@johndoexx][]) CHANGELOG expect(changelog.new_contributor_lines).to eq( [ '[@johndoe]: https://github.com/johndoe', '[@johndoex]: https://github.com/johndoex' ] ) end end # rubocop:enable RSpec/ExampleLength rubocop-ast-1.24.0/tasks/000077500000000000000000000000001434172225000151615ustar00rootroot00000000000000rubocop-ast-1.24.0/tasks/changelog.rake000066400000000000000000000014041434172225000177530ustar00rootroot00000000000000# frozen_string_literal: true autoload :Changelog, "#{__dir__}/changelog" namespace :changelog do %i[new fix change].each do |type| desc "Create a Changelog entry (#{type})" task type do path = Changelog::Entry.new(type: type).write cmd = "git add #{path}" system cmd puts "Entry '#{path}' created and added to git index" end end desc 'Merge entries and delete them' task :merge do raise 'No entries!' unless Changelog.pending? Changelog.new.merge!.and_delete! cmd = "git commit -a -m 'Update Changelog'" puts cmd system cmd end task :check_clean do next unless Changelog.pending? puts '*** Pending changelog entries!' puts 'Do `bundle exec rake changelog:merge`' exit(1) end end rubocop-ast-1.24.0/tasks/changelog.rb000066400000000000000000000103071434172225000174360ustar00rootroot00000000000000# frozen_string_literal: true if RUBY_VERSION < '2.6' puts 'Changelog utilities available only for Ruby 2.6+' exit(1) end # Changelog utility class Changelog # rubocop:disable Metrics/ClassLength ENTRIES_PATH = 'changelog/' FIRST_HEADER = /#{Regexp.escape("## master (unreleased)\n")}/m.freeze ENTRIES_PATH_TEMPLATE = "#{ENTRIES_PATH}%s_%s.md" TYPE_REGEXP = /#{Regexp.escape(ENTRIES_PATH)}([a-z]+)_/.freeze TYPE_TO_HEADER = { new: 'New features', fix: 'Bug fixes', change: 'Changes' }.freeze HEADER = /### (.*)/.freeze PATH = 'CHANGELOG.md' REF_URL = 'https://github.com/rubocop/rubocop-ast' MAX_LENGTH = 40 CONTRIBUTOR = '[@%s]: https://github.com/%s' SIGNATURE = Regexp.new(format(Regexp.escape("([@%s][])\n"), user: '(\w+)')) # New entry Entry = Struct.new(:type, :body, :ref_type, :ref_id, :user, keyword_init: true) do def initialize(type:, body: last_commit_title, ref_type: :pull, ref_id: nil, user: github_user) id, body = extract_id(body) ref_id ||= id || 'x' super end def write FileUtils.mkdir_p(ENTRIES_PATH) File.write(path, content) path end def path format(ENTRIES_PATH_TEMPLATE, type: type, name: str_to_filename(body)) end def content period = '.' unless body.end_with? '.' "* #{ref}: #{body}#{period} ([@#{user}][])\n" end def ref "[##{ref_id}](#{REF_URL}/#{ref_type}/#{ref_id})" end def last_commit_title `git log -1 --pretty=%B`.lines.first.chomp end def extract_id(body) /^\[Fixes #(\d+)\] (.*)/.match(body)&.captures || [nil, body] end def str_to_filename(str) str .downcase .split .each { |s| s.gsub!(/\W/, '') } .reject(&:empty?) .inject do |result, word| s = "#{result}_#{word}" return result if s.length > MAX_LENGTH s end end def github_user user = `git config --global credential.username`.chomp if user.empty? warn 'Set your username with `git config --global credential.username "myusernamehere"`' end user end end attr_reader :header, :rest def initialize(content: File.read(PATH), entries: Changelog.read_entries) require 'strscan' parse(content) @entries = entries end def and_delete! @entries.each_key do |path| File.delete(path) end end def merge! File.write(PATH, merge_content) self end def unreleased_content entry_map = parse_entries(@entries) merged_map = merge_entries(entry_map) merged_map.flat_map do |header, things| [ "### #{header}\n", *things, '' ] end.join("\n") end def merge_content [ @header, unreleased_content, @rest, *new_contributor_lines ].join("\n") end def self.pending? entry_paths.any? end def self.entry_paths Dir["#{ENTRIES_PATH}*"] end def self.read_entries entry_paths.to_h do |path| [path, File.read(path)] end end def new_contributor_lines contributors .map { |user| format(CONTRIBUTOR, user: user) } .reject { |line| @rest.include?(line) } end def contributors @entries.values.join("\n") .scan(SIGNATURE).flatten end private def merge_entries(entry_map) all = @unreleased.merge(entry_map) { |_k, v1, v2| v1.concat(v2) } canonical = TYPE_TO_HEADER.values.to_h { |v| [v, nil] } canonical.merge(all).compact end def parse(content) ss = StringScanner.new(content) @header = ss.scan_until(FIRST_HEADER) @unreleased = parse_release(ss.scan_until(/\n(?=## )/m)) @rest = ss.rest end # @return [Hash]] def parse_release(unreleased) unreleased .lines .map(&:chomp) .reject(&:empty?) .slice_before(HEADER) .to_h do |header, *entries| [HEADER.match(header)[1], entries] end end def parse_entries(path_content_map) changes = Hash.new { |h, k| h[k] = [] } path_content_map.each do |path, content| header = TYPE_TO_HEADER.fetch(TYPE_REGEXP.match(path)[1].to_sym) changes[header].concat(content.lines.map(&:chomp)) end changes end end rubocop-ast-1.24.0/tasks/check_commit.rake000066400000000000000000000015411434172225000204530ustar00rootroot00000000000000# 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-1.24.0/tasks/compile.rake000066400000000000000000000020371434172225000174570ustar00rootroot00000000000000# frozen_string_literal: true require 'oedipus_lex' Rake.application.rake_require 'oedipus_lex' def update_file(path) content = File.read(path) File.write(path, yield(content)) end ENCODING_COMMENT = '# frozen_string_literal: true' GENERATED_FILES = %w[ lib/rubocop/ast/node_pattern/parser.racc.rb lib/rubocop/ast/node_pattern/lexer.rex.rb ].freeze desc 'Generate the lexer and parser files.' task generate: %w[generate:lexer generate:parser] files = { lexer: 'lib/rubocop/ast/node_pattern/lexer.rex.rb', parser: 'lib/rubocop/ast/node_pattern/parser.racc.rb' } CLEAN.include(files.values) namespace :generate do files.each do |kind, filename| desc "Generate just the #{kind}" task kind => filename do update_file(filename) do |content| content.prepend ENCODING_COMMENT, "\n" unless content.start_with?(ENCODING_COMMENT) content.gsub 'module NodePattern', 'class NodePattern' end end end end rule '.racc.rb' => '.y' do |t| cmd = "bundle exec racc -l -v -o #{t.name} #{t.source}" sh cmd end rubocop-ast-1.24.0/tasks/cut_release.rake000066400000000000000000000030621434172225000203210ustar00rootroot00000000000000# 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 => 'changelog:check_clean' 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 rubocop-ast-1.24.0/tasks/debug.rake000066400000000000000000000025331434172225000171160ustar00rootroot00000000000000# frozen_string_literal: true desc 'Compile pattern to Ruby code for debugging purposes' task compile: :generate do if (pattern = ARGV[1]) require_relative '../lib/rubocop/ast' puts ::RuboCop::AST::NodePattern.new(pattern).compile_as_lambda else puts 'Usage:' puts " rake compile '(send nil? :example...)'" end exit(0) end desc 'Parse pattern to AST for debugging purposes' task parse: :generate do if (pattern = ARGV[1]) require_relative '../lib/rubocop/ast' puts ::RuboCop::AST::NodePattern::Parser.new.parse(pattern) else puts 'Usage:' puts " rake parse '(send nil? :example...)'" end exit(0) end desc 'Tokens of pattern for debugging purposes' task tokenize: :generate do if (pattern = ARGV[1]) require_relative '../lib/rubocop/ast' puts ::RuboCop::AST::NodePattern::Parser::WithMeta.new.tokenize(pattern).last else puts 'Usage:' puts " rake parse '(send nil? :example...)'" end exit(0) end desc 'Test pattern against ruby code' task test_pattern: :generate do if (pattern = ARGV[1]) && (ruby = ARGV[2]) require_relative '../lib/rubocop/ast' colorizer = ::RuboCop::AST::NodePattern::Compiler::Debug::Colorizer.new(pattern) puts colorizer.test(ruby).colorize else puts 'Usage:' puts " rake test-pattern '(send nil? :example...)' 'example(42)'" end exit(0) end