Showing preview only (459K chars total). Download the full file or copy to clipboard to get everything.
Repository: JetBrains/ruby-type-inference
Branch: master
Commit: df63525a226c
Files: 160
Total size: 406.4 KB
Directory structure:
gitextract_m6imoy85/
├── .gitignore
├── .travis.yml
├── FEATURES.md
├── LICENSE
├── README.md
├── arg_scanner/
│ ├── .gitignore
│ ├── Gemfile
│ ├── LICENSE.txt
│ ├── README.md
│ ├── Rakefile
│ ├── arg_scanner.gemspec
│ ├── bin/
│ │ ├── arg-scanner
│ │ ├── console
│ │ ├── rubymine-type-tracker
│ │ └── setup
│ ├── ext/
│ │ └── arg_scanner/
│ │ ├── arg_scanner.c
│ │ ├── arg_scanner.h
│ │ └── extconf.rb
│ ├── lib/
│ │ ├── arg_scanner/
│ │ │ ├── options.rb
│ │ │ ├── require_all.rb
│ │ │ ├── starter.rb
│ │ │ ├── state_tracker.rb
│ │ │ ├── type_tracker.rb
│ │ │ ├── version.rb
│ │ │ └── workspace.rb
│ │ └── arg_scanner.rb
│ ├── test/
│ │ ├── helper.rb
│ │ ├── test_args_info.rb
│ │ ├── test_call_info.rb
│ │ └── test_state_tracker.rb
│ └── util/
│ └── state_filter.rb
├── build.gradle
├── common/
│ ├── build.gradle
│ └── src/
│ └── main/
│ └── java/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── codeInsight/
│ ├── Injector.kt
│ ├── Logger.kt
│ └── PrintToStdoutLogger.kt
├── contract-creator/
│ ├── build.gradle
│ └── src/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── runtime/
│ └── signature/
│ └── server/
│ ├── SignatureServer.kt
│ ├── SignatureServerInjector.kt
│ └── serialisation/
│ └── ServerResponseBean.kt
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── ide-plugin/
│ ├── CHANGELOG.md
│ ├── build.gradle
│ ├── resources/
│ │ └── META-INF/
│ │ └── plugin.xml
│ └── src/
│ ├── com/
│ │ └── intellij/
│ │ └── execution/
│ │ └── executors/
│ │ ├── CollectStateExecutor.kt
│ │ └── RunWithTypeTrackerExecutor.java
│ ├── org/
│ │ └── jetbrains/
│ │ └── plugins/
│ │ └── ruby/
│ │ ├── IdePluginLogger.kt
│ │ ├── PluginResourceUtil.java
│ │ ├── RubyDynamicCodeInsightPluginInjector.kt
│ │ ├── ancestorsextractor/
│ │ │ ├── AncestorsExtractor.kt
│ │ │ └── RailsConsoleRunner.kt
│ │ ├── ruby/
│ │ │ ├── actions/
│ │ │ │ ├── ExportAncestorsActions.kt
│ │ │ │ ├── ExportAncesttorsDiffAction.kt
│ │ │ │ ├── ExportFileActionBase.kt
│ │ │ │ └── ImportExportContractsAction.kt
│ │ │ ├── codeInsight/
│ │ │ │ ├── ProjectLifecycleListenerImpl.kt
│ │ │ │ ├── RubyDynamicCodeInsightPluginAppLifecyctlListener.kt
│ │ │ │ ├── TrackerDataLoader.kt
│ │ │ │ ├── stateTracker/
│ │ │ │ │ ├── ClassHierarchySymbolProvider.kt
│ │ │ │ │ └── RubyClassHierarchyWithCaching.kt
│ │ │ │ ├── symbols/
│ │ │ │ │ └── structure/
│ │ │ │ │ └── RMethodSyntheticSymbol.java
│ │ │ │ └── types/
│ │ │ │ ├── RubyCollectStateRunner.kt
│ │ │ │ ├── RubyRunWithTypeTrackerRunner.kt
│ │ │ │ └── RubyTypeProvider.kt
│ │ │ ├── intentions/
│ │ │ │ ├── AddContractAnnotationIntention.java
│ │ │ │ ├── BaseRubyMethodIntentionAction.kt
│ │ │ │ └── RemoveCollectedInfoIntention.kt
│ │ │ ├── persistent/
│ │ │ │ └── TypeInferenceDirectory.kt
│ │ │ └── run/
│ │ │ └── configuration/
│ │ │ ├── CollectExecSettings.java
│ │ │ └── RunWithTypeTrackerRunConfigurationExtension.java
│ │ ├── settings/
│ │ │ ├── RubyTypeContractsConfigurable.kt
│ │ │ ├── RubyTypeContractsConfigurableUI.kt
│ │ │ └── RubyTypeContractsSettings.kt
│ │ └── util/
│ │ └── SignatureServerUtil.kt
│ └── test/
│ ├── java/
│ │ ├── CallStatCompletionTest.kt
│ │ └── org/
│ │ └── jetbrains/
│ │ └── plugins/
│ │ └── ruby/
│ │ └── ruby/
│ │ └── actions/
│ │ └── ImportExportTests.kt
│ └── testData/
│ ├── anonymous_module_method_call_test.rb
│ ├── call_info_of_nested_class_test.rb
│ ├── duplicates_in_callinfo_table_test.rb
│ ├── forget_call_info_when_arguments_number_changed_test_part_1.rb
│ ├── forget_call_info_when_arguments_number_changed_test_part_2.rb
│ ├── in_project_root_test/
│ │ ├── gem_like.rb
│ │ └── in_project_root_test.rb
│ ├── merge_test1.rb
│ ├── merge_test1_to_run.rb
│ ├── merge_test2.rb
│ ├── merge_test2_to_run.rb
│ ├── method_without_parameters_test.rb
│ ├── multiple_execution_test1.rb
│ ├── multiple_execution_test2.rb
│ ├── multiple_execution_test2_to_run.rb
│ ├── ref_links_test.rb
│ ├── ref_links_test_to_run.rb
│ ├── ruby_exec_part_2.rb
│ ├── ruby_exec_test.rb
│ ├── sample_kw_test.rb
│ ├── sample_kw_test_to_run.rb
│ ├── sample_test.rb
│ ├── sample_test_to_run.rb
│ ├── save_types_between_launches_test_part_1.rb
│ ├── save_types_between_launches_test_part_2.rb
│ ├── simple_call_info_collection_test.rb
│ ├── simple_call_info_collection_test_multiple_functions_test.rb
│ ├── simple_call_info_collection_with_multiple_arguments_test.rb
│ └── top_level_methods_call_info_collection_test.rb
├── ruby-call-signature/
│ ├── build.gradle
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── org/
│ │ └── jetbrains/
│ │ └── ruby/
│ │ └── codeInsight/
│ │ └── types/
│ │ └── signature/
│ │ ├── CallInfo.kt
│ │ ├── ClassInfo.kt
│ │ ├── GemInfo.kt
│ │ ├── MethodInfo.kt
│ │ ├── ParameterInfo.java
│ │ ├── RSignatureContract.java
│ │ ├── RSignatureContractContainer.kt
│ │ ├── RSignatureContractNode.java
│ │ ├── RTuple.java
│ │ ├── SignatureContract.kt
│ │ ├── SignatureInfo.kt
│ │ ├── contractTransition/
│ │ │ ├── ContractTransition.java
│ │ │ ├── ReferenceContractTransition.java
│ │ │ ├── TransitionHelper.java
│ │ │ └── TypedContractTransition.java
│ │ └── serialization/
│ │ ├── MethodInfoSerialization.kt
│ │ ├── RmcDirectory.kt
│ │ ├── SignatureContractSerialization.kt
│ │ └── TestSerialization.kt
│ └── test/
│ └── java/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── codeInsight/
│ └── types/
│ └── signature/
│ ├── GemInfoFromPathTest.kt
│ ├── SignatureContractMergeTest.kt
│ ├── SignatureContractSerializationTest.kt
│ └── SignatureContractTestBase.kt
├── settings.gradle
├── signature-viewer/
│ ├── build.gradle
│ └── src/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── runtime/
│ └── signature/
│ ├── DBViewer.kt
│ ├── EraseLocation.kt
│ ├── SignatureExport.kt
│ ├── SignatureImport.kt
│ ├── SignatureViewer.kt
│ └── SplitDB.kt
├── state-tracker/
│ ├── build.gradle
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── org/
│ │ └── jetbrains/
│ │ └── ruby/
│ │ └── stateTracker/
│ │ ├── RubyClassHierarchy.kt
│ │ └── RubyClassHierarchyLoader.kt
│ └── test/
│ └── java/
│ ├── org/
│ │ └── jetbrains/
│ │ └── ruby/
│ │ └── stateTracker/
│ │ ├── RubyClassHierarchyLoaderNonStandardModuleTypeTest.kt
│ │ └── RubyClassHierarchyLoaderTest.kt
│ └── testData/
│ ├── classes.json
│ └── non-standard-module-type.json
└── storage-server-api/
├── build.gradle
└── src/
├── main/
│ └── java/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── codeInsight/
│ └── types/
│ ├── signature/
│ │ └── serialization/
│ │ └── BlobSerialization.kt
│ └── storage/
│ └── server/
│ ├── DatabaseProvider.kt
│ ├── RSignatureProvider.java
│ ├── RSignatureStorage.java
│ ├── StorageException.java
│ ├── impl/
│ │ ├── IntIdTableWithPossibleDependency.kt
│ │ ├── RSignatureProviderImpl.kt
│ │ ├── RowConversions.kt
│ │ └── Schema.kt
│ └── testutil/
│ └── DatabaseTestUtils.kt
└── test/
└── java/
└── org/
└── jetbrains/
└── ruby/
└── codeInsight/
└── types/
└── storage/
└── server/
└── impl/
└── RSignatureProviderTest.kt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/build/
out/
*/build/
.gradle
.idea/
**/*.iml
**/.rakeTasks
arg_scanner/arg_scanner.iml
================================================
FILE: .travis.yml
================================================
language: ruby
dist: trusty
os:
- linux
# - osx
rvm:
- 2.3.3
- 2.4.2
- ruby-head
matrix:
fast_finish: true
allow_failures:
- rvm: ruby-head
services:
- mysql
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install mysql; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mysql.server start; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mysql -u root -e "CREATE USER 'travis'@'127.0.0.1' IDENTIFIED BY '';"; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mysql -u root -e "FLUSH PRIVILEGES;"; fi
- mysql -u root -e 'CREATE DATABASE ruby_type_contracts;'
- mysql -u root -e 'GRANT ALL ON ruby_type_contracts.* TO 'travis'@'127.0.0.1';'
- cd arg_scanner
script:
- gem install rake
- rake test
- rake install
- cd ..
- travis_wait 40 ./gradlew tasks
- ./gradlew -Dmysql.user.name=travis -Dmysql.user.password="" test
================================================
FILE: FEATURES.md
================================================
# ruby-type-inference features
This doc contains `ruby-type-inference` features which can be useful
for you after running your ruby program under type tracker:

## Type providing for method parameters

## Type providing for return value

## Side notes
As now RubyMine has more information about types it can provide
more reliable code completion, code analysis and other code insight features
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016-2017 JetBrains s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
Automated Type Contracts Generation [](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [](https://travis-ci.org/JetBrains/ruby-type-inference)
===================================
`ruby-type-inference` project is a completely new approach to
tackle the problems of Ruby dynamic nature and provide more reliable
symbol resolution and type inference. It collects some run time data
to build type contracts for the methods.
Every time a method is being called, some arguments of
particular types are being passed to it. Type Tracker collects
all such argument combinations and then builds a special contract
which satisfies all encountered argument type tuples.
The approach has its own pros and cons:
* The obtained contracts utilize real-world usages of code of
any complexity so it provides true results even if a method
utilizes dynamic Ruby features heavily.
* The completeness of the contracts obtained for a method highly
depends on the coverage of that method, including its callees.
That implies the need to merge the data obtained from the
different sources (e.g. different projects using the same gem).
This implementation addresses the stated coverage problem by providing
the possibility to merge any type contracts at any time.
## Usage
For simple usage you need to install the [Ruby Dynamic Code Insight](https://plugins.jetbrains.com/plugin/10227-ruby-dynamic-code-insight)
plugin for RubyMine. Then this plugin will require the [arg_scanner](https://rubygems.org/gems/arg_scanner) gem to be installed.
See [arg_scanner installation instruction](arg_scanner/README.md#installation) if you have problems while installation.
After that, you will have the possibility to run your programs under type tracker:

Or you can run your programs in terminal via the `rubymine-type-tracker` binary (But you have to keep your project opened
in RubyMine). E.g.:
```
rubymine-type-tracker bin/rails server
```
The `rubymine-type-tracker` binary is included into the [arg_scanner](https://rubygems.org/gems/arg_scanner) gem.
See [FEATURES.md](FEATURES.md) for understanding what benefits you will have after running your program under type tracker.
## Architecture
* **arg_scanner** is a gem with a native extension to attach to
ruby processes and trace and intercept all method calls to log
type-wise data flow in runtime.
See [`arg_scanner`] documentation for details on usage.
* The [**type contract processor**](contract-creator) server listens for
incoming type data (from `arg_scanner`) and processes it to a compact format.
The data stored may be used later for better code analysis and also
can be shared with other users.
* Code analysis clients (a RubyMine/IJ+Ruby [plugin](ide-plugin)) use the contract data
to provide features for the users such as code completion, better resolution, etc.
* (_todo_) Signature server receives contracts anonymously from the users and provides
a compiled contract collections for popular gems.
## Running project from sources
#### Prerequisites
The [`arg_scanner`] gem is used for collecting type information. It can be installed manually
to the target SDK and requires MRI Ruby at least 2.3.
#### Running type tracker
There are two possibilities to use the type tracker:
_(I)_ using IJ/RubyMine plugin or _(II)_ requiring it from Ruby code.
##### Using RubyMine plugin
The easiest way to run the plugin (and the most convenient for its development) is
running it with special gradle task against IJ Ultimate snapshot:
```
./gradlew ide-plugin:runIde
```
The task will compile the plugin, run IJ Ultimate with plugin "installed" in it.
There is no need in running anything manually in that case.
If you want to try it with existing RubyMine instance,
you should:
1. Build it via `./gradlew ide-plugin:buildPlugin`
2. Install plugin in the IDE
* Navigate to `File | Settings | Plugins | Install plugin from disk...`
* Locate plugin in `ide-plugin/build/distributions` and select.
* Restart IDE.
Note that due to API changes the plugin may be incompatible with older RM instances.
##### Using command line
1. In order to collect the data for the script needs a contract server to be up and running;
it could be run by running
```sh
./gradlew contract-creator:runServer --args path-to-db.mv.db
```
where `path-to-db.mv.db` is path where type contracts will be stored (H2 database file).
1. Run the ruby script to be processed via [`arg-scanner`](arg_scanner/bin/arg-scanner)
binary.
1. Use the data collected by the contract server.
## Contributions
Any kind of ideas, use cases, contributions and questions are very welcome
as the project is just incubating.
Please feel free to create issues for any sensible request.
[`arg_scanner`]: arg_scanner/README.md
================================================
FILE: arg_scanner/.gitignore
================================================
*.iml
.bundle/
.yardoc
Gemfile.lock
_yardoc/
coverage/
doc/
pkg/
spec/reports/
tmp/
*.bundle
*.so
*.o
*.a
mkmf.log
================================================
FILE: arg_scanner/Gemfile
================================================
source 'https://rubygems.org'
# Specify your gem's dependencies in arg_scanner.gemspec
gemspec
group :test do
gem 'test-unit'
end
================================================
FILE: arg_scanner/LICENSE.txt
================================================
The MIT License (MIT)
Copyright (c) 2017 JetBrains
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.
================================================
FILE: arg_scanner/README.md
================================================
# ArgScanner [](https://badge.fury.io/rb/arg_scanner)
`arg_scanner` is a gem with the purpose to track all method calls and
deliver the following information:
* Method signature (arguments, their names and kinds) and declaration place
* The types of argument variables given to each method call done
This information can be used then to calculate and use type contracts
for the analysed methods.
`arg_scanner` is meant to be used as a binary to run any other ruby executable
manually so including it in the `Gemfile` is not necessary.
## Installation
The recommended way to install it is to execute command:
```
gem install arg_scanner
```
**You will possibly need to install [native dependencies](#dependencies)**
## Building from sources
If you want to compile the gem from sources, just run the following commands:
```
bundle install
bundle exec rake install
```
If you have problems with native extension compilation, make sure you have
actual version of [ruby-core-source gem](https://github.com/os97673/debase-ruby_core_source) and
have [native dependencies](#dependencies) installed.
## Dependencies
##### [Glib](https://developer.gnome.org/glib/)
macOS: `brew install glib`
Debian/Ubuntu: `sudo apt install libglib2.0-dev`
Arch Linux: `sudo pacman -S glib2`
## Usage
`arg_scanner` provides the `arg-scanner` binary which receives any number of
arguments and executes the given command in type tracking mode,
for example:
```
arg-scanner --type-tracker --pipe-file-path=[pipe_file_path] bundle exec rake spec
```
`pipe_file_path` here is path to pipe file which is printed by server's stdout
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/JetBrains/ruby-type-inference
## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
================================================
FILE: arg_scanner/Rakefile
================================================
require "bundler/gem_tasks"
require "rake/extensiontask"
require 'rake/testtask'
BASE_TEST_FILE_LIST = Dir['test/**/test_*.rb']
task :build => :compile
Rake::ExtensionTask.new("arg_scanner") do |ext|
ext.lib_dir = "lib/arg_scanner"
end
desc "Test arg_scanner."
Rake::TestTask.new(:test => [:clean, :compile]) do |t|
t.libs += %w(./ext ./lib)
t.test_files = FileList[BASE_TEST_FILE_LIST]
t.verbose = true
end
task :test => :lib
task :default => [:clobber, :compile, :test]
================================================
FILE: arg_scanner/arg_scanner.gemspec
================================================
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'arg_scanner/version'
Gem::Specification.new do |spec|
spec.name = "arg_scanner"
spec.version = ArgScanner::VERSION
spec.authors = ["Nickolay Viuginov", "Valentin Fondaratov", "Vladimir Koshelev"]
spec.email = ["viuginov.nickolay@gmail.com", "fondarat@gmail.com", "vkkoshelev@gmail.com"]
spec.summary = %q{Program execution tracker to retrieve data types information}
spec.homepage = "https://github.com/jetbrains/ruby-type-inference"
spec.license = "MIT"
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
# if spec.respond_to?(:metadata)
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
# else
# raise "RubyGems 2.0 or newer is required to protect against " \
# "public gem pushes."
# end
spec.files = `git ls-files -z`.split("\x0").reject do |f|
f.match(%r{^(test|spec|features)/})
end
spec.bindir = "bin"
spec.executables = spec.files.grep(%r{^bin/}) {|f| File.basename(f)}
spec.require_paths = ["lib"]
spec.extensions = ["ext/arg_scanner/extconf.rb"]
spec.add_development_dependency "bundler", ">= 1.13"
spec.add_development_dependency "rake", ">= 12.0"
spec.add_development_dependency "rake-compiler"
spec.add_dependency "debase-ruby_core_source", ">= 0.10.4"
spec.add_dependency "native-package-installer", ">= 1.0.0"
end
================================================
FILE: arg_scanner/bin/arg-scanner
================================================
#!/usr/bin/env ruby
require 'optparse'
require 'arg_scanner/options'
require 'arg_scanner/version'
options = ArgScanner::OPTIONS
option_parser = OptionParser.new do |opts|
opts.banner = "arg-scanner #{ArgScanner::VERSION}" + <<~EOB
Usage: arg-scanner [OPTIONS] <ruby cmdline>
arg-scanner is a ruby script mediator supposed to be run from the command line or IDE.
The data will be sent to a signature server so it must be running during arg-scanner execution.
EOB
opts.separator "Options:"
opts.on("--type-tracker", "enable type tracker") do
options.enable_type_tracker = true
end
opts.on("--state-tracker", "enable state tracker") do
options.enable_state_tracker = true
end
opts.on("--no-type-tracker", "disable type tracker") do
options.enable_type_tracker = false
end
opts.on("--no-state-tracker", "disable state tracker") do
options.enable_state_tracker = false
end
opts.on("--output-dir=[Dir]", String, "specify output directory (ignored by type tracker)") do |dir|
options.output_dir = dir
end
opts.on("--catch-only-every-N-call=[N]", Integer, "randomly catches only 1/N of all calls to speed up performance (by default N = 1)") do |n|
options.catch_only_every_n_call = n
end
opts.on("--project-root=[PATH]", String, "Specify project's root directory to catch every call from this directory. "\
"Calls from other directories aren't guaranteed to be caught") do |path|
options.project_root = path
end
opts.on("--pipe-file-path=[PATH]", String, "Specify pipe file path to connect to server") do |path|
options.pipe_file_path = path
end
opts.on("--buffering", "enable buffering between arg-scanner and server. It speeds up arg-scanner but doesn't allow "\
"to use arg-scanner \"interactively\". Disabled by default") do |buffering|
options.buffering = buffering
end
end
begin
option_parser.parse! ARGV
rescue StandardError => e
puts option_parser
puts
puts e.message
exit 1
end
if ARGV.size < 1
puts option_parser
puts
puts "Ruby program to trace must be specified."
exit 1
end
options.set_env
old_opts = ENV['RUBYOPT'] || ''
starter = "-r #{File.expand_path(File.dirname(__FILE__))}/../lib/arg_scanner/starter"
unless old_opts.include? starter
ENV['RUBYOPT'] = starter
ENV['RUBYOPT'] += " #{old_opts}" if old_opts != ''
end
$0 = ARGV[0]
Kernel.exec *ARGV
================================================
FILE: arg_scanner/bin/console
================================================
#!/usr/bin/env ruby
require "bundler/setup"
require "arg_scanner"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start
================================================
FILE: arg_scanner/bin/rubymine-type-tracker
================================================
#!/usr/bin/env ruby
# This is small script for launching type tracker under RubyMine's provided server. Acts like arg-scanner wrapper
require 'optparse'
require 'arg_scanner/version'
require 'tmpdir'
require 'json'
option_parser = OptionParser.new do |opts|
opts.banner = <<~EOB
rubymine-type-tracker #{ArgScanner::VERSION}
Usage: rubymine-type-tracker <ruby script to execute>
rubymine-type-tracker is a ruby script for easy launching some command under
RubyMine's type tracker. The data will be sent to a server run by RubyMine.
So before launching this script be sure project is opened in RubyMine with
"Ruby Dynamic Code Insight" plugin installed.
EOB
end
begin
option_parser.parse! ARGV
if ARGV.size == 0
raise StandardError.new("")
end
rescue StandardError => e
puts option_parser
exit 1
end
dot_ruby_type_inference_dir = File.join(Dir.tmpdir, ".ruby-type-inference")
if File.directory?(dot_ruby_type_inference_dir)
match_jsons = Dir.foreach(dot_ruby_type_inference_dir).map do |file_name|
if file_name == '.' || file_name == '..'
next nil
end
json = JSON.parse(IO.read(File.join(dot_ruby_type_inference_dir, file_name)))
if json["projectPath"] != Dir.pwd
next nil
end
next json
end.select { |x| x != nil }
else
match_jsons = []
end
if match_jsons.count == 1
json = match_jsons[0]
elsif match_jsons.count > 1
STDERR.puts <<~EOB
Critical error! You may try to:\n
1. Close RubyMine
2. Clean #{dot_ruby_type_inference_dir}
3. Open RubyMine
EOB
exit 1
elsif match_jsons.count == 0
STDERR.puts <<~EOB
Error! You are possibly...
* launching this script under directory different from project
opened in RubyMine (please `cd` to dir firstly)
* haven't opened project in RubyMine
* haven't installed "Ruby Dynamic Code Insight" plugin in RubyMine
EOB
exit 1
end
to_exec = ["arg-scanner",
"--type-tracker",
"--project-root=#{json["projectPath"]}",
"--pipe-file-path=#{json["pipeFilePath"]}",
*ARGV]
Kernel.exec(*to_exec)
================================================
FILE: arg_scanner/bin/setup
================================================
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
================================================
FILE: arg_scanner/ext/arg_scanner/arg_scanner.c
================================================
#include "arg_scanner.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>
#include <netinet/in.h>
#include <glib.h>
//#define DEBUG_ARG_SCANNER 1
#if RUBY_API_VERSION_CODE >= 20500
#if (RUBY_RELEASE_YEAR == 2017 && RUBY_RELEASE_MONTH == 10 && RUBY_RELEASE_DAY == 10) //workaround for 2.5.0-preview1
#define TH_CFP(thread) ((rb_control_frame_t *)(thread)->ec.cfp)
#else
#define TH_CFP(thread) ((rb_control_frame_t *)(thread)->ec->cfp)
#endif
#else
#define TH_CFP(thread) ((rb_control_frame_t *)(thread)->cfp)
#endif
#ifdef DEBUG_ARG_SCANNER
#define LOG(f, args...) { fprintf(stderr, "DEBUG: '%s'=", #args); fprintf(stderr, f, ##args); fflush(stderr); }
#else
#define LOG(...) {}
#endif
#define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))
typedef struct rb_trace_arg_struct rb_trace_arg_t;
VALUE mArgScanner = Qnil;
int types_ids[20];
static VALUE c_signature;
/**
* Contains info related to explicitly passed args
* For example:
* def foo(a, b = 1); end
*
* `b` passed here implicitly:
* foo(1)
*
* But here explicitly:
* foo(1, 10)
*/
typedef struct
{
ssize_t call_info_explicit_argc; // Number of arguments that was explicitly passed by user
char **call_info_kw_explicit_args; // kw arguments names that was explicitly passed by user (null terminating array)
} call_info_t;
typedef struct
{
char *receiver_name;
char *method_name;
char *args_info;
char *path;
char *return_type_name;
ssize_t explicit_argc; // Number of arguments that was explicitly passed by user
int lineno;
int is_in_project_root; // Can be 0, 1 or -1 when project_root is not specified
} signature_t;
void Init_arg_scanner();
static const char *ARG_SCANNER_EXIT_COMMAND = "EXIT";
static const char *EMPTY_VALUE = "";
static const int MAX_NUMBER_OF_MISSED_CALLS = 10;
/**
* There we keep information about signatures that have already been sent to server in order to not sent them again
*/
static GTree *sent_to_server_tree;
/**
* Here we store map with key: signature_t and value: int number (how many times method was called with the same args)
* If we got that any method is called with the same args more than MAX_NUMBER_OF_MISSED_CALLS times in a row then
* we will ignore it.
*/
static GTree *number_missed_calls_tree;
static GSList *call_stack = NULL;
static char *get_args_info(const char *const *explicit_kw_args);
static VALUE handle_call(VALUE self, VALUE tp);
static VALUE handle_return(VALUE self, VALUE tp);
static VALUE destructor(VALUE self);
static const char *calc_sane_class_name(VALUE ptr);
// returns Qnil if ready; or string containing error message otherwise
static VALUE check_if_arg_scanner_ready(VALUE self);
// For testing
static VALUE get_args_info_rb(VALUE self);
static VALUE get_call_info_rb(VALUE self);
static call_info_t get_call_info();
static bool is_call_info_needed();
static void call_info_t_free(call_info_t s)
{
free(s.call_info_kw_explicit_args);
}
static void signature_t_free(signature_t *s)
{
free(s->receiver_name);
free(s->method_name);
free(s->args_info);
free(s->path);
free(s->return_type_name);
free(s);
}
// Free signature_t partially leaving parts that are used in sent_to_server_tree_comparator
// @see_also sent_to_server_tree_comparator
static void signature_t_free_partially(signature_t *s)
{
free(s->receiver_name);
s->receiver_name = NULL;
free(s->method_name);
s->method_name = NULL;
}
// Comparator for number_missed_calls_tree.
static gint
number_missed_calls_tree_comparator(gconstpointer x, gconstpointer y, gpointer user_data_ignored) {
const signature_t *a = x;
const signature_t *b = y;
int ret;
// Comparison using lineno and path theoretically should guarantees us unique.
// And compare lineno firstly because it's faster O(1) than comparing path which is O(path_len)
ret = a->lineno - b->lineno;
if (ret != 0) return ret;
ret = strcmp(a->path, b->path);
if (ret != 0) return ret;
return 0;
}
// Comparator for sent_to_server_tree.
// If you want to change the way it compare then don't forget to
// change signature_t_free_partially accordingly
// @see_also signature_t_free_partially
static gint
sent_to_server_tree_comparator(gconstpointer x, gconstpointer y, gpointer user_data_ignored) {
const signature_t *a = x;
const signature_t *b = y;
int ret;
ret = number_missed_calls_tree_comparator(x, y, user_data_ignored);
if (ret != 0) return ret;
if (a->args_info != NULL && b->args_info != NULL) {
ret = strcmp(a->args_info, b->args_info);
if (ret != 0) return ret;
}
ret = strcmp(a->return_type_name, b->return_type_name);
if (ret != 0) return ret;
return 0;
}
inline int start_with(const char *str, const char *prefix) {
if (str == NULL || prefix == NULL) {
return -1;
}
while (*str != '\0' && *prefix != '\0') {
if (*str != *prefix) {
return 0;
}
str++;
prefix++;
}
return 1;
}
FILE *pipe_file = NULL;
static char *project_root = NULL;
static int catch_only_every_n_call = 1;
static int file_exists(const char *file_path) {
return access(file_path, F_OK) != -1;
}
static VALUE init(VALUE self, VALUE pipe_file_path, VALUE buffering,
VALUE project_root_local, VALUE catch_only_every_n_call_local) {
if (pipe_file_path != Qnil) {
pipe_file_path = rb_file_s_expand_path(1, &pipe_file_path); // https://ruby-doc.org/core-2.2.0/File.html#method-c-expand_path
const char *pipe_file_path_c = StringValueCStr(pipe_file_path);
if (!file_exists(pipe_file_path_c)) {
fprintf(stderr, "Specified pipe file: %s doesn't exists\n", pipe_file_path_c);
exit(1);
}
pipe_file = fopen(pipe_file_path_c, "w");
if (pipe_file == NULL) {
fprintf(stderr, "Cannot open pipe file \"%s\" with write access\n", pipe_file_path_c);
exit(1);
}
int buffering_disabled = buffering == Qnil;
if (buffering_disabled) {
setbuf(pipe_file, NULL);
}
}
if (project_root_local != Qnil) {
project_root = strdup(StringValueCStr(project_root_local));
}
if (catch_only_every_n_call_local != Qnil) {
if (sscanf(StringValueCStr(catch_only_every_n_call_local), "%d", &catch_only_every_n_call) != 1) {
fprintf(stderr, "Please specify number in --catch-only-every-N-call arg\n");
exit(1);
}
srand(time(0));
}
return Qnil;
}
void Init_arg_scanner() {
mArgScanner = rb_define_module("ArgScanner");
rb_define_module_function(mArgScanner, "handle_call", handle_call, 1);
rb_define_module_function(mArgScanner, "handle_return", handle_return, 1);
rb_define_module_function(mArgScanner, "get_args_info", get_args_info_rb, 0);
rb_define_module_function(mArgScanner, "get_call_info", get_call_info_rb, 0);
rb_define_module_function(mArgScanner, "destructor", destructor, 0);
rb_define_module_function(mArgScanner, "check_if_arg_scanner_ready", check_if_arg_scanner_ready, 0);
rb_define_module_function(mArgScanner, "init", init, 4);
sent_to_server_tree = g_tree_new_full(/*key_compare_func =*/sent_to_server_tree_comparator,
/*key_compare_data =*/NULL,
/*key_destroy_func =*/(GDestroyNotify)signature_t_free,
/*value_destroy_func =*/NULL);
// key_destroy_func is NULL because we will use the same keys for number_missed_calls_tree
// and sent_to_server_tree. And all memory management is done by sent_to_server_tree
number_missed_calls_tree = g_tree_new_full(/*key_compare_func =*/number_missed_calls_tree_comparator,
/*key_compare_data =*/NULL,
/*key_destroy_func =*/NULL,
/*value_destroy_func =*/NULL);
}
inline void push_to_call_stack(signature_t *signature) {
call_stack = g_slist_prepend(call_stack, (gpointer) signature);
}
inline signature_t *pop_from_call_stack() {
if (call_stack == NULL) {
return NULL;
}
signature_t *ret = (signature_t *) call_stack->data;
GSList *old_head = call_stack;
call_stack = g_slist_remove_link(call_stack, old_head);
g_slist_free_1(old_head);
return ret;
}
inline int is_call_stack_empty() {
return call_stack == NULL;
}
/**
* Looks at the object at the top of this stack without removing it from the stack.
*/
inline signature_t *top_of_call_stack() {
if (call_stack == NULL) {
return NULL;
}
return (signature_t *) call_stack[0].data;
}
rb_control_frame_t *
my_rb_vm_get_binding_creatable_next_cfp(const rb_thread_t *th, const rb_control_frame_t *cfp)
{
while (!RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp)) {
if (cfp->iseq) {
return (rb_control_frame_t *)cfp;
}
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
}
return 0;
}
static VALUE exit_from_handle_call_skipping_call() {
push_to_call_stack(NULL);
return Qnil;
}
static VALUE
handle_call(VALUE self, VALUE tp)
{
signature_t sign_temp;
memset(&sign_temp, 0, sizeof(sign_temp));
sign_temp.lineno = FIX2INT(rb_funcall(tp, rb_intern("lineno"), 0)); // Convert Ruby's Fixnum to C language int
VALUE path = rb_funcall(tp, rb_intern("path"), 0);
path = rb_file_s_expand_path(1, &path); // https://ruby-doc.org/core-2.2.0/File.html#method-c-expand_path
sign_temp.path = StringValueCStr(path);
int is_in_project_root = start_with(sign_temp.path, project_root);
if (project_root != NULL && !is_in_project_root) {
signature_t *peek = top_of_call_stack();
if (!is_call_stack_empty() && (peek == NULL || !(peek->is_in_project_root))) {
return exit_from_handle_call_skipping_call();
}
}
if (project_root == NULL || !is_in_project_root) {
int number_of_missed_calls = (int)g_tree_lookup(number_missed_calls_tree, &sign_temp);
if (number_of_missed_calls > MAX_NUMBER_OF_MISSED_CALLS) {
return exit_from_handle_call_skipping_call();
}
}
if (catch_only_every_n_call != 1 && rand() % catch_only_every_n_call != 0) {
return exit_from_handle_call_skipping_call();
}
signature_t *sign = (signature_t *) calloc(1, sizeof(*sign));
sign->is_in_project_root = is_in_project_root;
sign->lineno = sign_temp.lineno;
sign->path = strdup(sign_temp.path);
sign->method_name = strdup(rb_id2name(SYM2ID(rb_funcall(tp, rb_intern("method_id"), 0))));
sign->explicit_argc = -1;
#ifdef DEBUG_ARG_SCANNER
LOG("Getting args info for %s %s %d \n", sign->method_name, sign->path, sign->lineno);
#endif
call_info_t info;
info.call_info_kw_explicit_args = NULL;
if (is_call_info_needed()) {
info = get_call_info();
sign->explicit_argc = info.call_info_explicit_argc;
}
sign->args_info = get_args_info(info.call_info_kw_explicit_args);
call_info_t_free(info);
if (sign->args_info != NULL && strlen(sign->args_info) >= 1000) {
signature_t_free(sign);
return exit_from_handle_call_skipping_call();
}
push_to_call_stack(sign);
return Qnil;
}
static VALUE
handle_return(VALUE self, VALUE tp)
{
signature_t *sign = pop_from_call_stack();
if (sign == NULL) {
return Qnil;
}
VALUE defined_class = rb_funcall(tp, rb_intern("defined_class"), 0);
VALUE receiver_name = rb_mod_name(defined_class);
// if defined_class is nil then it means that method is invoked from anonymous module.
// Then trying to extract name of it's anonymous module. For more details see
// CallStatCompletionTest#testAnonymousModuleMethodCall
if (receiver_name == Qnil) {
VALUE this = rb_funcall(tp, rb_intern("self"), 0);
receiver_name = rb_funcall(this, rb_intern("to_s"), 0);
}
VALUE return_type_name = rb_funcall(tp, rb_intern("return_value"), 0);
sign->receiver_name = strdup(StringValueCStr(receiver_name));
sign->return_type_name = strdup(calc_sane_class_name(return_type_name));
signature_t *sign_in_sent_to_server_tree = g_tree_lookup(sent_to_server_tree, sign);
if (sign_in_sent_to_server_tree == NULL) {
// Resets number of missed calls to 0
g_tree_insert(number_missed_calls_tree, /*key = */sign, /*value = */0);
// GTree will free memory allocated by sign by itself
g_tree_insert(sent_to_server_tree, /*key = */sign, /*value = */sign);
if (pipe_file != NULL) {
fprintf(pipe_file,
"{\"method_name\":\"%s\",\"call_info_argc\":\"%d\",\"args_info\":\"%s\",\"visibility\":\"%s\","
"\"path\":\"%s\",\"lineno\":\"%d\",\"receiver_name\":\"%s\",\"return_type_name\":\"%s\"}\n",
sign->method_name,
sign->explicit_argc,
sign->args_info != NULL ? sign->args_info : "",
"PUBLIC",
sign->path,
sign->lineno,
sign->receiver_name,
sign->return_type_name);
}
signature_t_free_partially(sign);
} else if (project_root == NULL || !sign->is_in_project_root) {
signature_t_free(sign);
int found = (int) g_tree_lookup(number_missed_calls_tree, sign_in_sent_to_server_tree);
g_tree_insert(number_missed_calls_tree, /*key = */sign_in_sent_to_server_tree, /*value = */found + 1);
}
return Qnil;
}
static call_info_t
get_call_info() {
rb_thread_t *thread = ruby_current_thread;
rb_control_frame_t *cfp = TH_CFP(thread);
call_info_t empty;
empty.call_info_kw_explicit_args = NULL;
empty.call_info_explicit_argc = -1;
cfp += 3;
cfp = my_rb_vm_get_binding_creatable_next_cfp(thread, cfp);
if(cfp->iseq == NULL || cfp->pc == NULL || cfp->iseq->body == NULL) {
return empty;
}
const rb_iseq_t *iseq = (const rb_iseq_t *) cfp->iseq;
ptrdiff_t pc = cfp->pc - cfp->iseq->body->iseq_encoded;
const VALUE *iseq_original = rb_iseq_original_iseq(iseq);
int indent;
for (indent = 1; indent < 6; indent++) {
VALUE insn = iseq_original[pc - indent];
int tmp = (int)insn;
if(0 < tmp && tmp < 256) {
if(indent < 3) {
return empty;
}
call_info_t info;
struct rb_call_info *ci = (struct rb_call_info *)iseq_original[pc - indent + 1];
info.call_info_explicit_argc = ci->orig_argc;
info.call_info_kw_explicit_args = NULL;
if (ci->flag & VM_CALL_KWARG) {
struct rb_call_info_kw_arg *kw_args = ((struct rb_call_info_with_kwarg *)ci)->kw_arg;
size_t kwArgSize = kw_args->keyword_len;
VALUE kw_ary = rb_ary_new_from_values(kw_args->keyword_len, kw_args->keywords);
info.call_info_kw_explicit_args = (char **) malloc((kwArgSize + 1)*sizeof(*(info.call_info_kw_explicit_args)));
int i;
for (i = kwArgSize -1 ; i >= 0; --i) {
VALUE kw = rb_ary_pop(kw_ary);
const char *kw_name = rb_id2name(SYM2ID(kw));
info.call_info_kw_explicit_args[i] = kw_name;
}
info.call_info_kw_explicit_args[kwArgSize] = NULL;
} else {
info.call_info_kw_explicit_args = malloc(sizeof(*info.call_info_kw_explicit_args));
info.call_info_kw_explicit_args[0] = NULL;
}
return info;
}
}
return empty;
}
static const char*
calc_sane_class_name(VALUE ptr)
{
VALUE klass = rb_obj_class(ptr);
const char* klass_name;
// may be false, see `object.c#rb_class_get_superclass`
if (klass == Qfalse) {
klass_name = "<err>";
}
else
{
klass_name = rb_class2name(klass);
}
// returned value may be NULL, see `variable.c#rb_class2name`
if (klass_name == NULL)
{
klass_name = "<err>";
}
return klass_name;
}
static char *
fast_join_array(char sep, size_t count, const char **strings)
{
size_t lengths[count + 1];
size_t i;
char *result;
lengths[0] = 0;
for (i = 0; i < count; i++)
{
const char *str = strings[i];
size_t length;
if (!str)
length = 0;
else
length = strlen(str) + (i > 0); // 1 for separator before
lengths[i + 1] = lengths[i] + length;
}
result = (char *)malloc(sizeof(*result) * (1 + lengths[count]));
for (i = 0; i < count; i++)
{
const char *str = strings[i];
if (str)
{
int start = lengths[i];
if (i > 0)
result[start++] = sep;
memcpy(result + start, str, sizeof(*result) * (lengths[i + 1] - start));
}
}
result[lengths[count]] = 0;
return result;
}
static char *
fast_join(char sep, size_t count, ...)
{
char *strings[count];
size_t i;
va_list ap;
va_start(ap, count);
for (i = 0; i < count; i++)
{
strings[i] = va_arg(ap, char *);
}
va_end(ap);
return fast_join_array(sep, count, strings);
}
/**
* Checks that `container` contains `element`
*/
static int contains(const char *const *container, const char *element) {
if (container == NULL || element == NULL) {
return 0;
}
const char *const *iterator = container;
while (*iterator != NULL) {
if (strcmp(*iterator, element) == 0) {
return 1;
}
++iterator;
}
return 0;
}
#define JOIN_KW_NAMES_AND_TYPES_BUF_SIZE 2048
static char join_kw_names_and_types_buf[JOIN_KW_NAMES_AND_TYPES_BUF_SIZE];
/**
* Null terminating array which contains strings of explicitly passed kw args.
* It's used for join_kw_names_and_types
*/
static const char *const *join_kw_names_and_types_explicit_kw_args = NULL;
/**
* This function is used for concatenating hash keys and value's types.
* Be sure that buf is at least JOIN_KW_NAMES_AND_TYPES_BUF_SIZE bytes.
* If join_kw_names_and_types_buf size = JOIN_KW_NAMES_AND_TYPES_BUF_SIZE
* isn't enough then this buf will contain invalid information
*/
static int join_kw_names_and_types(VALUE key, VALUE val, VALUE ignored) {
const char *kw_name = rb_id2name(SYM2ID(key));
const char *kw_type = calc_sane_class_name(val);
const char *const *explicit_kw_args_iterator = join_kw_names_and_types_explicit_kw_args;
// Just such behaviour: when join_kw_names_and_types_explicit_kw_args is
// not provided then consider every kw arg as explicitly passed by user
int is_explicit = explicit_kw_args_iterator == NULL;
if (explicit_kw_args_iterator != NULL) {
while(*explicit_kw_args_iterator != NULL) {
if (strcmp(*explicit_kw_args_iterator, kw_name) == 0) {
is_explicit = 1;
break;
}
++explicit_kw_args_iterator;
}
}
if (is_explicit) {
// Check that buf is not empty
if (join_kw_names_and_types_buf[0] != '\0') {
strncat(join_kw_names_and_types_buf, ";", JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
}
strncat(join_kw_names_and_types_buf, "KEYREST,", JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
strncat(join_kw_names_and_types_buf, kw_type, JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
strncat(join_kw_names_and_types_buf, ",", JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
strncat(join_kw_names_and_types_buf, kw_name, JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1);
}
return ST_CONTINUE;
}
static char*
get_args_info(const char *const *explicit_kw_args)
{
rb_thread_t *thread;
rb_control_frame_t *cfp;
thread = ruby_current_thread;
cfp = TH_CFP(thread);
cfp += 2;
VALUE *ep = cfp->ep;
ep -= cfp->iseq->body->local_table_size;
size_t param_size = cfp->iseq->body->param.size;
size_t lead_num = cfp->iseq->body->param.lead_num;
size_t opt_num = cfp->iseq->body->param.opt_num;
size_t post_num = cfp->iseq->body->param.post_num;
unsigned int has_rest = cfp->iseq->body->param.flags.has_rest;
unsigned int has_kw = cfp->iseq->body->param.flags.has_kw;
unsigned int has_kwrest = cfp->iseq->body->param.flags.has_kwrest;
unsigned int has_block = cfp->iseq->body->param.flags.has_block;
LOG("%d\n", param_size);
LOG("%d\n", lead_num);
LOG("%d\n", opt_num);
LOG("%d\n", post_num);
LOG("%d\n", has_rest);
LOG("%d\n", has_kw);
LOG("%d\n", has_kwrest);
LOG("%d\n", has_block);
if (param_size == 0) {
return 0;
}
const char **types = (const char **)malloc(param_size * sizeof(*types));
size_t i, ans_iterator;
int types_iterator;
ans_iterator = 0;
int new_version_flag = strcmp(RUBY_VERSION, "2.4.0") >= 0 ? 1 : 0;
LOG("%d\n", new_version_flag);
for(i = param_size - 1 - new_version_flag, types_iterator = 0; (size_t)types_iterator < param_size; i--, types_iterator++)
{
types[types_iterator] = calc_sane_class_name(ep[i - 1]);
types_ids[types_iterator] = i - 1;
LOG("Type #%d=%s\n", types_iterator, types[types_iterator])
}
types_iterator--;
if(has_kw) {
param_size--;
}
char **ans = (char **)malloc(param_size * sizeof(*ans));
for(i = 0; i < lead_num; i++, ans_iterator++, types_iterator--)
{
const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
ans[ans_iterator] = fast_join(',', 3, "REQ", types[types_iterator], name);
}
for(i = 0; i < opt_num; i++, ans_iterator++, types_iterator--)
{
const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
ans[ans_iterator] = fast_join(',', 3, "OPT", types[types_iterator], name);
}
for(i = 0; i < has_rest; i++, ans_iterator++, types_iterator--)
{
const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
ans[ans_iterator] = fast_join(',', 3, "REST", types[types_iterator], name);
}
for(i = 0; i < post_num; i++, ans_iterator++, types_iterator--)
{
const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
ans[ans_iterator] = fast_join(',', 3, "POST", types[types_iterator], name);
}
if(cfp->iseq->body->param.keyword != NULL)
{
const ID *keywords = cfp->iseq->body->param.keyword->table;
size_t kw_num = cfp->iseq->body->param.keyword->num;
size_t required_num = cfp->iseq->body->param.keyword->required_num;
size_t rest_start = cfp->iseq->body->param.keyword->rest_start;
LOG("%d %d\n", kw_num, required_num)
for(i = 0; i < required_num; i++, ans_iterator++, types_iterator--)
{
ID key = keywords[i];
ans[ans_iterator] = fast_join(',', 3, "KEYREQ", types[types_iterator], rb_id2name(key));
}
for(i = required_num; i < kw_num; i++, types_iterator--)
{
ID key = keywords[i];
const char *name = rb_id2name(key);
if (explicit_kw_args == NULL || contains(explicit_kw_args, name)) {
ans[ans_iterator++] = fast_join(',', 3, "KEY", types[types_iterator], name);
}
}
if (param_size - has_block > 1 && has_kwrest && TYPE(ep[types_ids[types_iterator]]) == T_FIXNUM) {
types_iterator--;
}
if (has_kwrest)
{
char *buf = malloc(JOIN_KW_NAMES_AND_TYPES_BUF_SIZE * sizeof(*buf));
buf[0] = '\0';
join_kw_names_and_types_buf[0] = '\0';
join_kw_names_and_types_explicit_kw_args = explicit_kw_args;
// This function call will concatenate info into join_kw_names_and_types_buf
rb_hash_foreach(ep[types_ids[types_iterator]], join_kw_names_and_types, Qnil);
// Checking that join_kw_names_and_types_buf isn't possibly containing invalid info.
// See join_kw_names_and_types documentation to understand why it can be invalid
size_t len = strlen(join_kw_names_and_types_buf);
if (len > 0 && len < JOIN_KW_NAMES_AND_TYPES_BUF_SIZE - 1) {
strncpy(buf, join_kw_names_and_types_buf, JOIN_KW_NAMES_AND_TYPES_BUF_SIZE);
ans[ans_iterator++] = buf;
}
join_kw_names_and_types_explicit_kw_args = NULL;
types_iterator--;
}
}
for(i = 0; i < has_block; i++, ans_iterator++, types_iterator--)
{
const char* name = rb_id2name(cfp->iseq->body->local_table[ans_iterator]);
ans[ans_iterator] = fast_join(',', 3, "BLOCK", types[types_iterator], name);
}
LOG("%d\n", ans_iterator)
char *answer = fast_join_array(';', ans_iterator, ans);
for(i = 0; i < ans_iterator; i++) {
LOG("free2 %d %d =%s= \n", ans[i], strlen(ans[i]), ans[i]);
free(ans[i]);
}
LOG("%d %d %d", ans_iterator, param_size, types_iterator);
assert(types_iterator <= 0);
free(types);
free(ans);
return answer;
}
static VALUE
get_args_info_rb(VALUE self)
{
call_info_t info;
info.call_info_kw_explicit_args = NULL;
if (is_call_info_needed()) {
info = get_call_info();
}
char *args_info = get_args_info(info.call_info_kw_explicit_args);
call_info_t_free(info);
VALUE ret = args_info ? rb_str_new_cstr(args_info) : Qnil;
free(args_info);
return ret;
}
static VALUE
get_call_info_rb(VALUE self)
{
if (is_call_info_needed())
{
call_info_t info = get_call_info();
VALUE ans;
ans = rb_ary_new();
rb_ary_push(ans, LONG2FIX(info.call_info_explicit_argc));
if (info.call_info_kw_explicit_args != NULL) {
const char *const *kwarg = info.call_info_kw_explicit_args;
int explicit_kw_count = 0;
while (*kwarg != NULL) {
++explicit_kw_count;
++kwarg;
}
char *answer = fast_join_array(',', explicit_kw_count, info.call_info_kw_explicit_args);
rb_ary_push(ans, rb_str_new_cstr(answer));
free(answer);
}
call_info_t_free(info);
return ans;
}
else
{
return Qnil;
}
}
static bool
is_call_info_needed()
{
rb_thread_t *thread;
rb_control_frame_t *cfp;
thread = ruby_current_thread;
cfp = TH_CFP(thread);
cfp += 2;
return (cfp->iseq->body->param.flags.has_opt
|| cfp->iseq->body->param.flags.has_kwrest
|| cfp->iseq->body->param.flags.has_rest
|| (cfp->iseq->body->param.keyword != NULL && cfp->iseq->body->param.keyword->required_num == 0));
}
static VALUE
check_if_arg_scanner_ready(VALUE self) {
char error_msg[1024];
if (pipe_file == NULL) {
snprintf(error_msg, sizeof(error_msg)/sizeof(*error_msg), "Pipe file is not specified");
return rb_str_new_cstr(error_msg);
}
return Qnil;
}
static VALUE
destructor(VALUE self) {
g_tree_destroy(sent_to_server_tree);
g_tree_destroy(number_missed_calls_tree);
fprintf(pipe_file, "%s\n", ARG_SCANNER_EXIT_COMMAND);
fclose(pipe_file);
free(project_root);
return Qnil;
}
================================================
FILE: arg_scanner/ext/arg_scanner/arg_scanner.h
================================================
#ifndef ARG_SCANNER_H
#define ARG_SCANNER_H 1
#include "ruby.h"
#include "vm_core.h"
#include "version.h"
#include "iseq.h"
#include "method.h"
#endif /* ARG_SCANNER_H */
================================================
FILE: arg_scanner/ext/arg_scanner/extconf.rb
================================================
require "mkmf"
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
require "debase/ruby_core_source"
require "native-package-installer"
class NilClass
def empty?; true; end
end
# Just a replacement of have_header because have_header searches not recursively :(
def real_have_header(header_name)
if (have_header(header_name))
return true
end
yes_msg = "checking for #{header_name}... yes"
no_msg = "checking for #{header_name}... no"
include_env = ENV["C_INCLUDE_PATH"]
if !include_env.empty? && !Dir.glob("#{include_env}/**/#{header_name}").empty?
puts yes_msg
return true
end
if !Dir.glob("/usr/include/**/#{header_name}").empty?
puts yes_msg
return true
end
puts no_msg
return false
end
if !real_have_header('glib.h') &&
!NativePackageInstaller.install(:alt_linux => "glib2-devel",
:debian => "libglib2.0-dev",
:redhat => "glib2-devel",
:arch_linux => "glib2",
:homebrew => "glib",
:macports => "glib2",
:msys2 => "glib2")
exit(false)
end
hdrs = proc {
have_header("vm_core.h") and
have_header("iseq.h") and
have_header("version.h") and
have_header("vm_core.h") and
have_header("vm_insnhelper.h") and
have_header("vm_core.h") and
have_header("method.h")
}
# Allow use customization of compile options. For example, the
# following lines could be put in config_options to to turn off
# optimization:
# $CFLAGS='-fPIC -fno-strict-aliasing -g3 -ggdb -O2 -fPIC'
config_file = File.join(File.dirname(__FILE__), 'config_options.rb')
load config_file if File.exist?(config_file)
if ENV['debase_debug']
$CFLAGS+=' -Wall -Werror -g3'
end
$CFLAGS += ' `pkg-config --cflags --libs glib-2.0`'
$DLDFLAGS += ' `pkg-config --cflags --libs glib-2.0`'
dir_config("ruby")
if !Debase::RubyCoreSource.create_makefile_with_core(hdrs, "arg_scanner/arg_scanner")
STDERR.print("Makefile creation failed\n")
STDERR.print("*************************************************************\n\n")
STDERR.print(" NOTE: If your headers were not found, try passing\n")
STDERR.print(" --with-ruby-include=PATH_TO_HEADERS \n\n")
STDERR.print("*************************************************************\n\n")
exit(1)
end
================================================
FILE: arg_scanner/lib/arg_scanner/options.rb
================================================
require 'ostruct'
module ArgScanner
OPTIONS = OpenStruct.new(
:enable_type_tracker => ENV['ARG_SCANNER_ENABLE_TYPE_TRACKER'],
:enable_state_tracker => ENV['ARG_SCANNER_ENABLE_STATE_TRACKER'],
:output_directory => ENV['ARG_SCANNER_DIR'],
:catch_only_every_n_call => ENV['ARG_SCANNER_CATCH_ONLY_EVERY_N_CALL'] || 1,
:project_root => ENV['ARG_SCANNER_PROJECT_ROOT'],
:pipe_file_path => ENV['ARG_SCANNER_PIPE_FILE_PATH'] || '',
:buffering => ENV['ARG_SCANNER_BUFFERING']
)
def OPTIONS.set_env
ENV['ARG_SCANNER_ENABLE_TYPE_TRACKER'] = self.enable_type_tracker ? "1" : nil
ENV['ARG_SCANNER_ENABLE_STATE_TRACKER'] = self.enable_state_tracker ? "1" : nil
ENV['ARG_SCANNER_DIR'] = self.output_directory
ENV['ARG_SCANNER_CATCH_ONLY_EVERY_N_CALL'] = self.catch_only_every_n_call.to_s
ENV['ARG_SCANNER_PROJECT_ROOT'] = self.project_root
ENV['ARG_SCANNER_PIPE_FILE_PATH'] = self.pipe_file_path
ENV['ARG_SCANNER_BUFFERING'] = self.buffering ? "1" : nil
end
end
================================================
FILE: arg_scanner/lib/arg_scanner/require_all.rb
================================================
# Copyright (c) 2009 Jarmo Pertman
# 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.
module RequireAll
# A wonderfully simple way to load your code.
#
# The easiest way to use require_all is to just point it at a directory
# containing a bunch of .rb files. These files can be nested under
# subdirectories as well:
#
# require_all 'lib'
#
# This will find all the .rb files under the lib directory and load them.
# The proper order to load them in will be determined automatically.
#
# If the dependencies between the matched files are unresolvable, it will
# throw the first unresolvable NameError.
#
# You can also give it a glob, which will enumerate all the matching files:
#
# require_all 'lib/**/*.rb'
#
# It will also accept an array of files:
#
# require_all Dir.glob("blah/**/*.rb").reject { |f| stupid_file(f) }
#
# Or if you want, just list the files directly as arguments:
#
# require_all 'lib/a.rb', 'lib/b.rb', 'lib/c.rb', 'lib/d.rb'
#
def require_all(*args)
# Handle passing an array as an argument
args.flatten!
options = {:method => :require}
options.merge!(args.pop) if args.last.is_a?(Hash)
if args.empty?
puts "no files were loaded due to an empty Array" if $DEBUG
return false
end
if args.size > 1
# Expand files below directories
files = args.map do |path|
if File.directory? path
Dir[File.join(path, '**', '*.rb')]
else
path
end
end.flatten
else
arg = args.first
begin
# Try assuming we're doing plain ol' require compat
stat = File.stat(arg)
if stat.file?
files = [arg]
elsif stat.directory?
files = Dir.glob File.join(arg, '**', '*.rb')
else
raise ArgumentError, "#{arg} isn't a file or directory"
end
rescue SystemCallError
# If the stat failed, maybe we have a glob!
files = Dir.glob arg
# Maybe it's an .rb file and the .rb was omitted
if File.file?(arg + '.rb')
file = arg + '.rb'
options[:method] != :autoload ? Kernel.send(options[:method], file) : __autoload(file, file, options)
return true
end
# If we ain't got no files, the glob failed
raise LoadError, "no such file to load -- #{arg}" if files.empty?
end
end
return if files.empty?
if options[:method] == :autoload
files.map! { |file_| [file_, File.expand_path(file_)] }
files.each do |file_, full_path|
__autoload(file_, full_path, options)
end
return true
end
files.map! { |file_| File.expand_path file_ }
files.sort!
begin
failed = []
first_name_error = nil
# Attempt to load each file, rescuing which ones raise NameError for
# undefined constants. Keep trying to successively reload files that
# previously caused NameErrors until they've all been loaded or no new
# files can be loaded, indicating unresolvable dependencies.
files.each do |file_|
begin
Kernel.send(options[:method], file_)
rescue NameError => ex
failed << file_
first_name_error ||= ex
rescue ArgumentError => ex
# Work around ActiveSuport freaking out... *sigh*
#
# ActiveSupport sometimes throws these exceptions and I really
# have no idea why. Code loading will work successfully if these
# exceptions are swallowed, although I've run into strange
# nondeterministic behaviors with constants mysteriously vanishing.
# I've gone spelunking through dependencies.rb looking for what
# exactly is going on, but all I ended up doing was making my eyes
# bleed.
#
# FIXME: If you can understand ActiveSupport's dependencies.rb
# better than I do I would *love* to find a better solution
raise unless ex.message["is not missing constant"]
STDERR.puts "Warning: require_all swallowed ActiveSupport 'is not missing constant' error"
STDERR.puts ex.backtrace[0..9]
end
end
# If this pass didn't resolve any NameErrors, we've hit an unresolvable
# dependency, so raise one of the exceptions we encountered.
if failed.size == files.size
raise first_name_error
else
files = failed
end
end until failed.empty?
true
end
# Works like require_all, but paths are relative to the caller rather than
# the current working directory
def require_rel(*paths)
# Handle passing an array as an argument
paths.flatten!
return false if paths.empty?
source_directory = File.dirname caller.first.sub(/:\d+$/, '')
paths.each do |path|
require_all File.join(source_directory, path)
end
end
# Loads all files like require_all instead of requiring
def load_all(*paths)
require_all paths, :method => :load
end
# Loads all files by using relative paths of the caller rather than
# the current working directory
def load_rel(*paths)
paths.flatten!
return false if paths.empty?
source_directory = File.dirname caller.first.sub(/:\d+$/, '')
paths.each do |path|
require_all File.join(source_directory, path), :method => :load
end
end
# Performs Kernel#autoload on all of the files rather than requiring immediately.
#
# Note that all Ruby files inside of the specified directories should have same module name as
# the directory itself and file names should reflect the class/module names.
# For example if there is a my_file.rb in directories dir1/dir2/ then
# there should be a declaration like this in my_file.rb:
# module Dir1
# module Dir2
# class MyFile
# ...
# end
# end
# end
#
# If the filename and namespaces won't match then my_file.rb will be loaded into wrong module!
# Better to fix these files.
#
# Set $DEBUG=true to see how files will be autoloaded if experiencing any problems.
#
# If trying to perform autoload on some individual file or some inner module, then you'd have
# to always specify *:base_dir* option to specify where top-level namespace resides.
# Otherwise it's impossible to know the namespace of the loaded files.
#
# For example loading only my_file.rb from dir1/dir2 with autoload_all:
#
# autoload_all File.dirname(__FILE__) + '/dir1/dir2/my_file',
# :base_dir => File.dirname(__FILE__) + '/dir1'
#
# WARNING: All modules will be created even if files themselves aren't loaded yet, meaning
# that all the code which depends of the modules being loaded or not will not work, like usages
# of define? and it's friends.
#
# Also, normal caveats of using Kernel#autoload apply - you have to remember that before
# applying any monkey-patches to code using autoload, you'll have to reference the full constant
# to load the code before applying your patch!
def autoload_all(*paths)
paths.flatten!
return false if paths.empty?
require "pathname"
options = {:method => :autoload}
options.merge!(paths.pop) if paths.last.is_a?(Hash)
paths.each do |path|
require_all path, {:base_dir => path}.merge(options)
end
end
# Performs autoloading relatively from the caller instead of using current working directory
def autoload_rel(*paths)
paths.flatten!
return false if paths.empty?
require "pathname"
options = {:method => :autoload}
options.merge!(paths.pop) if paths.last.is_a?(Hash)
source_directory = File.dirname caller.first.sub(/:\d+$/, '')
paths.each do |path|
file_path = Pathname.new(source_directory).join(path).to_s
require_all file_path, {:method => :autoload,
:base_dir => source_directory}.merge(options)
end
end
private
def __autoload(file, full_path, options)
last_module = "Object" # default constant where namespaces are created into
begin
base_dir = Pathname.new(options[:base_dir]).realpath
rescue Errno::ENOENT
raise LoadError, ":base_dir doesn't exist at #{options[:base_dir]}"
end
Pathname.new(file).realpath.descend do |entry|
# skip until *entry* is same as desired directory
# or anything inside of it avoiding to create modules
# from the top-level directories
next if (entry <=> base_dir) < 0
# get the module into which a new module is created or
# autoload performed
mod = Object.class_eval(last_module)
without_ext = entry.basename(entry.extname).to_s
const = without_ext.split("_").map {|word| word.capitalize}.join
if entry.directory?
mod.class_eval "module #{const} end"
last_module += "::#{const}"
else
mod.class_eval do
puts "autoloading #{mod}::#{const} from #{full_path}" if $DEBUG
autoload const, full_path
end
end
end
end
end
================================================
FILE: arg_scanner/lib/arg_scanner/starter.rb
================================================
# starter.rb is loaded with "ruby -r" option from bin/arg-scanner
# or by IDEA also with "ruby -r" option
unless ENV["ARG_SCANNER_ENABLE_STATE_TRACKER"].nil?
require_relative 'state_tracker'
ArgScanner::StateTracker.new
end
unless ENV["ARG_SCANNER_ENABLE_TYPE_TRACKER"].nil?
require_relative 'arg_scanner'
require_relative 'type_tracker'
# instantiating type tracker will enable calls tracing and sending the data
ArgScanner::TypeTracker.instance
end
================================================
FILE: arg_scanner/lib/arg_scanner/state_tracker.rb
================================================
require "set"
require_relative "require_all"
require_relative "workspace"
module ArgScanner
class StateTracker
def initialize
@workspace = Workspace.new
@workspace.on_process_start
at_exit do
begin
require_extra_libs
@workspace.open_output_json("classes") { |file| print_json(file) }
ensure
@workspace.on_process_exit
end
end
end
private
def require_extra_libs
begin
RequireAll.require_all Rails.root.join('lib')
rescue Exception => e
end
begin
Rails.application.eager_load!
rescue Exception => e
end
end
def print_json(file)
result = {
:top_level_constants => parse_top_level_constants,
:modules => modules_to_json,
:load_path => $:
}
require "json"
file.puts(JSON.dump(result))
end
def parse_top_level_constants
Module.constants.select { |const| Module.const_defined?(const)}.map do |const|
begin
value = Module.const_get(const)
(!value.is_a? Module) ? {
:name => const,
:class_name => value.class,
:extended => get_extra_methods(value)} : nil
rescue Exception => e
end
end.compact
end
def get_extra_methods(value)
value.methods - value.public_methods
end
def method_to_json(method)
ret = {
:name => method.name,
:parameters => method.parameters
}
unless method.source_location.nil?
ret[:path] = method.source_location[0]
ret[:line] = method.source_location[1]
end
ret
rescue Exception => e
nil
end
def module_to_json(mod)
ret = {
:name => mod.to_s,
:type => mod.class.to_s,
:singleton_class_ancestors => mod.singleton_class.ancestors.map{|it| it.to_s},
:ancestors => mod.ancestors.map{|it| it.to_s}, # map to_s is needed because for example "Psych" parsed not correctly into JSON format
# it's parsed as: "{}\n" check it by launching in rails console: "JSON.generate(Psych)"
:class_methods => mod.methods(false).map {|method| method_to_json(mod.method(method))}.compact,
:instance_methods => mod.instance_methods(false).map {|method| method_to_json(mod.instance_method(method))}.compact
}
ret[:superclass] = mod.superclass if mod.is_a? Class
ret
rescue Exception => e
nil
end
def modules_to_json
ObjectSpace.each_object(Module).map {|mod| module_to_json(mod)}
end
end
end
================================================
FILE: arg_scanner/lib/arg_scanner/type_tracker.rb
================================================
require 'set'
require 'socket'
require 'singleton'
require 'thread'
require_relative 'options'
module ArgScanner
class TypeTrackerPerformanceMonitor
def initialize
@enable_debug = ENV["ARG_SCANNER_DEBUG"]
@call_counter = 0
@handled_call_counter = 0
@submitted_call_counter = 0
@old_handled_call_counter = 0
@time = Time.now
end
def on_call
@submitted_call_counter += 1
end
def on_return
@call_counter += 1
if enable_debug && call_counter % 100000 == 0
$stderr.puts("calls #{call_counter} handled #{handled_call_counter} submitted #{submitted_call_counter}"\
"delta #{handled_call_counter - old_handled_call_counter} time #{Time.now - @time}")
@old_handled_call_counter = handled_call_counter
@time = Time.now
end
end
def on_handled_return
@handled_call_counter += 1
end
private
attr_accessor :submitted_call_counter
attr_accessor :handled_call_counter
attr_accessor :old_handled_call_counter
attr_accessor :call_counter
attr_accessor :enable_debug
end
class TypeTracker
include Singleton
def initialize
ArgScanner.init(ENV['ARG_SCANNER_PIPE_FILE_PATH'], ENV['ARG_SCANNER_BUFFERING'],
ENV['ARG_SCANNER_PROJECT_ROOT'], ENV['ARG_SCANNER_CATCH_ONLY_EVERY_N_CALL'])
@enable_debug = ENV["ARG_SCANNER_DEBUG"]
@performance_monitor = if @enable_debug then TypeTrackerPerformanceMonitor.new else nil end
TracePoint.trace(:call, &ArgScanner.method(:handle_call))
TracePoint.trace(:return, &ArgScanner.method(:handle_return))
error_msg = ArgScanner.check_if_arg_scanner_ready()
if error_msg != nil
STDERR.puts error_msg
Process.exit(1)
end
ObjectSpace.define_finalizer(self, proc { ArgScanner.destructor() })
end
attr_accessor :enable_debug
attr_accessor :performance_monitor
attr_accessor :prefix
end
end
================================================
FILE: arg_scanner/lib/arg_scanner/version.rb
================================================
module ArgScanner
VERSION = "0.3.3"
end
================================================
FILE: arg_scanner/lib/arg_scanner/workspace.rb
================================================
module ArgScanner
class Workspace
def initialize
@dir = ENV["ARG_SCANNER_DIR"] || "."
@pid_file = @dir+"/#{Process.pid}.pid"
end
def on_process_start
File.open(@pid_file, "w") {}
end
def open_output_json(prefix)
path = @dir + "/#{prefix}-#{Time.now.strftime('%Y-%m-%d_%H-%M-%S')}-#{Process.pid}.json"
path_tmp_name = path + ".temp"
File.open(path_tmp_name, "w") { |file| yield file }
require 'fileutils'
FileUtils.mv(path_tmp_name, path)
end
def on_process_exit
require 'fileutils'
FileUtils.rm(@pid_file)
end
end
end
================================================
FILE: arg_scanner/lib/arg_scanner.rb
================================================
require "arg_scanner/version"
require "arg_scanner/arg_scanner"
require "arg_scanner/type_tracker"
require "arg_scanner/state_tracker"
module ArgScanner
# Your code goes here...
end
================================================
FILE: arg_scanner/test/helper.rb
================================================
$LOAD_PATH.unshift(File.dirname(__dir__) + '/../lib')
require "test-unit"
require "arg_scanner"
class TestTypeTracker
include Singleton
attr_reader :last_args_info
attr_reader :last_call_info
def initialize
@tp = TracePoint.new(:call, :return) do |tp|
case tp.event
when :call
ArgScanner.handle_call(tp)
@last_args_info = ArgScanner.get_args_info.split ';'
@last_call_info = ArgScanner.get_call_info
when :return
ArgScanner.handle_return(tp)
end
end
end
def enable(*args, &b)
@tp.enable *args, &b
end
def signatures
Thread.current[:signatures] ||= Array.new
end
end
================================================
FILE: arg_scanner/test/test_args_info.rb
================================================
#!/usr/bin/env ruby
require File.expand_path("helper", File.dirname(__FILE__))
require 'date'
class TestArgsInfoWrapper
def foo(a); end
def foo2(a, b = 1); end
def foo3(**rest); end
def foo4(kw: :symbol, **rest1); end
def foo5(kw:, **rest); end
def foo6(a, *rest, b); end
def initialize
# @trace = TracePoint.new(:call) do |tp|
# case tp.event
# when :call
# tp.binding.local_variables.each { |v| p tp.binding.eval v.to_s }
# ArgScanner.handle_call(tp.lineno, tp.method_id, tp.path)
# @args_info = ArgScanner.get_args_info
# p @args_info
# end
# end
end
end
class TestArgsInfo < Test::Unit::TestCase
# @!attribute [r] type_tracker
# @return [TestTypeTracker]
attr_reader :type_tracker
def setup
@args_info_wrapper = TestArgsInfoWrapper.new
@type_tracker = TestTypeTracker.instance
end
def teardown
end
def test_simple_kwrest
type_tracker.enable do
@args_info_wrapper.foo3(a: Date.new, kkw: 'hi')
end
assert_equal ["KEYREST,Date,a", "KEYREST,String,kkw"], type_tracker.last_args_info
end
def test_empty_kwrest
type_tracker.enable do
@args_info_wrapper.foo3()
end
assert_equal [], type_tracker.last_args_info
end
def test_req_and_opt_arg
type_tracker.enable do
@args_info_wrapper.foo2(Date.new)
end
assert_equal "REQ,Date,a", type_tracker.last_args_info[0]
assert type_tracker.last_args_info[1] == "OPT,Fixnum,b" || type_tracker.last_args_info[1] == "OPT,Integer,b"
end
def test_optkw_and_empty_kwrest
type_tracker.enable do
@args_info_wrapper.foo4(kw: Date.new)
end
assert_equal ["KEY,Date,kw"], type_tracker.last_args_info
end
def test_reqkw_and_empty_kwrest
type_tracker.enable do
@args_info_wrapper.foo5(kw: Date.new)
end
assert_equal ["KEYREQ,Date,kw"], type_tracker.last_args_info
end
def test_reqkw_and_kwrest
type_tracker.enable do
@args_info_wrapper.foo5(kw: Date.new, aa: true, bb: '1')
end
assert_equal ["KEYREQ,Date,kw", "KEYREST,TrueClass,aa", "KEYREST,String,bb"], type_tracker.last_args_info
end
def test_optkw_and_kwrest
type_tracker.enable do
@args_info_wrapper.foo4(aa: :symbol, bb: '1')
end
assert_equal ["KEYREST,Symbol,aa", "KEYREST,String,bb"], type_tracker.last_args_info
end
def test_optkw_passed_and_kwrest
type_tracker.enable do
@args_info_wrapper.foo4(kw: 'bla-bla', aa: :symbol, bb: '1')
end
assert_equal ["KEY,String,kw", "KEYREST,Symbol,aa", "KEYREST,String,bb"], type_tracker.last_args_info
end
def test_rest
type_tracker.enable do
@args_info_wrapper.foo6(1, 'hi', Date.new, '1')
end
assert type_tracker.last_args_info[0] == "REQ,Fixnum,a" || type_tracker.last_args_info[0] == "REQ,Integer,a"
assert type_tracker.last_args_info[1] == "REST,Array,rest"
assert type_tracker.last_args_info[2] == "POST,String,b"
end
def test_empty_rest
type_tracker.enable do
@args_info_wrapper.foo6(1, '1')
end
assert type_tracker.last_args_info[0] == "REQ,Fixnum,a" || type_tracker.last_args_info[0] == "REQ,Integer,a"
assert type_tracker.last_args_info[1] == "REST,Array,rest"
assert type_tracker.last_args_info[2] == "POST,String,b"
end
end
================================================
FILE: arg_scanner/test/test_call_info.rb
================================================
#!/usr/bin/env ruby
require File.expand_path("helper", File.dirname(__FILE__))
class TestCallInfoWrapper
def sqr(z1 = 10, z2 = 11, z3 = 13, z4 = 14, z5, z6, z7, z8, y: '0', x: "40")
end
def sqr2(z0, z1 = 2, z2 = 10, z3 = 2, z4 = 0, y: 1, x: 30, z: '40')
end
def foo(a, b, c, *d, e)
end
def foo2(*args)
end
def foo3(b: 2, c: '3', **args)
end
def foo4(b: 2, c:, d: "1", dd: 1, ddd: '111', **args)
end
def foo5(b)
end
end
class TestCallInfo < Test::Unit::TestCase
# @!attribute [r] type_tracker
# @return [TestTypeTracker]
attr_reader :type_tracker
def setup
@call_info_wrapper = TestCallInfoWrapper.new
@type_tracker = TestTypeTracker.instance
end
def teardown
end
def test_simple
type_tracker.enable do
@call_info_wrapper.sqr2(10, 11)
end
assert_not_nil type_tracker.last_call_info
#assert type_tracker.last_call_info.size == 2
#assert type_tracker.last_call_info[0] == "sqr2"
assert_equal 2, type_tracker.last_call_info[0]
end
def test_simple_req_arg
type_tracker.enable do
@call_info_wrapper.foo5(10)
end
assert_nil type_tracker.last_call_info
end
def test_simple_kw
type_tracker.enable do
@call_info_wrapper.sqr2(10, 11, x: 10, y: 1)
end
assert_not_nil type_tracker.last_call_info
#assert type_tracker.last_call_info.size == 3
#assert type_tracker.last_call_info[0] == "sqr2"
assert_equal 4, type_tracker.last_call_info[0]
assert_equal "x,y", type_tracker.last_call_info[1]
end
def test_rest
type_tracker.enable do
@call_info_wrapper.foo2(1, 2, 3, 4, 5, 6, 7, 8)
end
assert_not_nil type_tracker.last_call_info
#assert type_tracker.last_call_info.size == 2
#assert type_tracker.last_call_info[0] == "foo2"
assert_equal 8, type_tracker.last_call_info[0]
end
def test_post_and_rest
type_tracker.enable do
@call_info_wrapper.foo(1, 2, 3, 4, 5, 6, 7, 8)
end
#coz it is obvious that all the arguments were passed (they are all required)
assert_not_nil type_tracker.last_call_info
#assert type_tracker.last_call_info.size == 2
#assert type_tracker.last_call_info[0] == "foo"
#assert type_tracker.last_call_info[0] == 8
end
def test_kwrest
type_tracker.enable do
@call_info_wrapper.foo3(a: 1, b: 2, c: 3, d: 4)
end
assert_not_nil type_tracker.last_call_info
#assert type_tracker.last_call_info.size == 3
#assert type_tracker.last_call_info[0] == "foo3"
assert_equal 4, type_tracker.last_call_info[0]
assert_equal "a,b,c,d", type_tracker.last_call_info[1]
end
def test_rest_and_reqkw_args
type_tracker.enable do
@call_info_wrapper.foo4(b: "hello", c: 'world', e: 1, f: "not")
end
assert_not_nil type_tracker.last_call_info
#assert type_tracker.last_call_info.size == 3
#assert type_tracker.last_call_info[0] == "foo4"
assert_equal 4, type_tracker.last_call_info[0]
assert_equal "b,c,e,f", type_tracker.last_call_info[1]
end
end
================================================
FILE: arg_scanner/test/test_state_tracker.rb
================================================
require 'test/unit'
require 'tempfile'
require 'fileutils'
require 'json'
class StateTrackerTest < Test::Unit::TestCase
class << self
#Runs only once at start
def startup
file = Tempfile.new("StateTracker")
dirname = file.path
FileUtils.rm(dirname)
file.close
begin
FileUtils.makedirs(dirname)
system("echo exit | ARG_SCANNER_DIR=\"#{dirname}\" ARG_SCANNER_ENABLE_STATE_TRACKER=\"1\" irb -r\"#{File.dirname(__dir__)}/lib/arg_scanner/starter.rb\" 2> /dev/null")
files = Dir["#{dirname}/*.json"]
@@json = JSON.parse(File.read(files[0]))
ensure
FileUtils.rm_rf(dirname)
end
end
end
def test_has_struct
assert_not_nil(get_class_with_name("Struct"))
end
def test_symbol_is_fine
symbol = get_class_with_name("Symbol")
assert_not_nil(symbol)
assert_equal(symbol["type"], "Class")
assert_equal(symbol["superclass"], "Object")
assert_not_nil(symbol["singleton_class_ancestors"].find_index("Kernel"))
assert_not_nil(symbol["ancestors"].find_index("Comparable"))
assert_not_nil(get_class_method(symbol, "all_symbols"))
assert_not_nil(get_instance_method(symbol, "match"))
parameters = get_instance_method(symbol, "match")['parameters']
assert_not_nil(parameters)
assert_equal(parameters[0][0], (RUBY_VERSION < "2.4.0") ? "req" : "rest")
end
def test_loaded_path_is_fine
assert_not_nil(@@json["load_path"])
assert_not_nil(@@json["load_path"][0])
end
def test_constant_is_fine
assert_not_nil(@@json["top_level_constants"])
assert_not_nil(@@json["top_level_constants"][0])
assert_not_nil(@@json["top_level_constants"][0]["name"])
assert_not_nil(@@json["top_level_constants"][0]["class_name"])
assert_not_nil(@@json["top_level_constants"][0]["extended"])
end
private
def get_class_method(symbol, name)
get_named_entity(symbol, "class_methods", name)
end
def get_instance_method(symbol, name)
get_named_entity(symbol, "instance_methods", name)
end
def get_class_with_name(name)
get_named_entity(@@json, "modules", name)
end
def get_named_entity(obj, index, name)
obj[index].find {|entity| entity["name"] == name}
end
end
================================================
FILE: arg_scanner/util/state_filter.rb
================================================
#!/usr/bin/env ruby
require 'json'
require 'set'
if ARGV.length < 3
puts("state_filter.rb <in-file> <out-file> [<list-of-names]")
exit
end
json = JSON.parse(File.read(ARGV[0]))
modules2names = {}
json["modules"].each {|mod| modules2names[mod["name"]] = mod}
visited = Set.new
queue = Queue.new
ARGV[2..-1].each do |it|
visited.add(it)
queue.push(it)
end
until queue.empty? do
elem = modules2names[queue.pop] || next
(elem["singleton_class_included"] + elem["included"] + [elem["superclass"]]).each do |mod|
queue.push(mod) if visited.add?(mod)
end
end
output_modules = visited.map do |mod|
modules2names[mod]
end.compact
File.write(ARGV[1], JSON.pretty_generate({:modules => output_modules, :load_path => json["load_path"]}))
================================================
FILE: build.gradle
================================================
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7"
classpath 'org.apache.httpcomponents:httpclient:4.5.2'
}
}
allprojects {
repositories {
mavenCentral()
maven {
url 'https://dl.bintray.com/kotlin/exposed'
}
}
apply plugin: 'java'
apply plugin: 'kotlin'
def project = it
dependencies {
if (project.name != 'ide-plugin') {
compile 'org.jetbrains:annotations:15.0'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
testCompile 'junit:junit:4.12'
testCompile 'com.h2database:h2:1.4.193'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
test {
systemProperties System.properties
testLogging {
exceptionFormat = 'full'
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '4.10.2'
}
subprojects {
if (it.name in ['storage-server-api', 'lambda-update-handler', 'lambda-put-handler', 'contract-creator', 'state-tracker', 'ide-plugin']) {
dependencies {
compile 'com.google.code.gson:gson:2.8.0'
}
}
}
================================================
FILE: common/build.gradle
================================================
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
dependencies {
}
sourceSets {
main.java.srcDirs = ['src/main/java']
main.kotlin.srcDirs = ['src/main/java']
test.kotlin.srcDirs = ['src/test/java']
}
================================================
FILE: common/src/main/java/org/jetbrains/ruby/codeInsight/Injector.kt
================================================
package org.jetbrains.ruby.codeInsight
/**
* Dependency injection mechanism
*/
interface Injector {
fun <T> getLogger(cl: Class<T>): Logger
}
@Volatile
private var _injector: Injector? = null
val injector: Injector
get() {
return _injector ?: throw IllegalStateException("Injector must be initialized before any usage")
}
// Because the we don't know anything about injector initializators we assume that it can be
// potentially multi threaded but necessity of injector initialization thread safety isn't really investigated
@Synchronized
fun initInjector(injector: Injector) {
check(_injector == null) {
"Injector must be initialized only once"
}
_injector = injector
}
================================================
FILE: common/src/main/java/org/jetbrains/ruby/codeInsight/Logger.kt
================================================
package org.jetbrains.ruby.codeInsight
interface Logger {
fun info(msg: String)
}
================================================
FILE: common/src/main/java/org/jetbrains/ruby/codeInsight/PrintToStdoutLogger.kt
================================================
package org.jetbrains.ruby.codeInsight
import java.text.SimpleDateFormat
import java.util.*
private val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
/**
* Most basic [Logger] implementation
*/
class PrintToStdoutLogger(private val category: String) : Logger {
constructor(cl : Class<*>) : this(cl.name)
override fun info(msg: String) {
println("${format.format(Calendar.getInstance())} [$category] $msg")
}
}
================================================
FILE: contract-creator/build.gradle
================================================
sourceSets {
main.java.srcDirs = ['src']
}
dependencies {
compile project(':common')
compile project(':ruby-call-signature')
compile project(':storage-server-api')
// compile 'com.h2database:h2:1.4.193'
}
task runServer(type: JavaExec) {
classpath sourceSets.main.runtimeClasspath
main = 'org.jetbrains.ruby.runtime.signature.server.SignatureServerKt'
}
================================================
FILE: contract-creator/src/org/jetbrains/ruby/runtime/signature/server/SignatureServer.kt
================================================
package org.jetbrains.ruby.runtime.signature.server
import com.google.gson.Gson
import com.google.gson.JsonParseException
import com.google.gson.JsonSyntaxException
import org.jetbrains.ruby.codeInsight.initInjector
import org.jetbrains.ruby.codeInsight.types.signature.CallInfo
import org.jetbrains.ruby.codeInsight.types.storage.server.DatabaseProvider
import org.jetbrains.ruby.codeInsight.types.storage.server.impl.CallInfoTable
import org.jetbrains.ruby.runtime.signature.server.serialisation.ServerResponseBean
import org.jetbrains.ruby.runtime.signature.server.serialisation.toCallInfo
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong
import java.util.logging.Logger
import kotlin.concurrent.thread
private const val EXIT_COMMAND = "EXIT";
fun main(args: Array<String>) {
initInjector(SignatureServerInjector)
parseArgs(args).let {
DatabaseProvider.connectToDB(it.dbFilePath, isDefaultDatabase = true)
}
val pipeFileName = SignatureServer().runServerAsync(isDaemon = false)
println("Pass this to arg-scanner via --pipe-file-path: $pipeFileName")
// Intercept Ctrl+C
Runtime.getRuntime().addShutdownHook(thread(start = false) {
File(pipeFileName).delete()
})
}
private data class ParsedArgs(val dbFilePath: String)
private fun parseArgs(args: Array<String>): ParsedArgs {
if (args.size != 1) {
System.err.println("""
One argument required: path-to-h2-db-file
Or if you run it via gradle: ./gradlew contract-creator:runServer --args path-to-db
""".trimIndent())
System.exit(1)
}
return ParsedArgs(args.single())
}
class SignatureServer {
companion object {
private const val LOCAL_STORAGE_SIZE_LIMIT = 128
@Suppress("ObjectPropertyName")
private val _runningServers: MutableList<SignatureServer> = Collections.synchronizedList(mutableListOf())
val runningServers: List<SignatureServer>
get() = _runningServers
}
private val LOGGER = Logger.getLogger("SignatureServer")
private val callInfoContainer = LinkedList<CallInfo>()
private val gson = Gson()
private val queue = ArrayBlockingQueue<String>(10024)
private val isReady = AtomicBoolean(true)
private var previousPollEndedWithFlush = false
val readTime = AtomicLong(0)
val jsonTime = AtomicLong(0)
val addTime = AtomicLong(0)
private val signatureHandler = SignatureHandler()
private val pollJsonThread = PollJsonThread()
fun isProcessingRequests() = !isReady.get()
private fun generateTempFilePath(prefix: String = ""): String {
val dirForTempFiles = System.getProperty("java.io.tmpdir")
return Paths.get(dirForTempFiles, prefix + UUID.randomUUID()).toString()
}
/**
* @return pipe filename path which should be passed to arg-scanner
*/
fun runServerAsync(isDaemon: Boolean): String {
isReady.set(false)
_runningServers.add(this)
LOGGER.info("Starting server")
val pipeFileName = generateTempFilePath(prefix = "ruby-type-inference-pipe-")
val proc: Process = Runtime.getRuntime().exec("mkfifo $pipeFileName")
if (proc.waitFor() != 0) {
throw RuntimeException("Cannot create pipe file")
}
signatureHandler.pipeFilePath = pipeFileName
signatureHandler.isDaemon = isDaemon
signatureHandler.start()
pollJsonThread.isDaemon = isDaemon
pollJsonThread.start()
return pipeFileName
}
var afterFlushListener: (() -> Unit)? = null
var afterExitListener: (() -> Unit)? = null
/**
* @return true when client won't send data anymore
*/
private fun pollJson(): Boolean {
val jsonString by lazy { if (previousPollEndedWithFlush) queue.take() else queue.poll() }
if (callInfoContainer.size > LOCAL_STORAGE_SIZE_LIMIT || jsonString == null || jsonString == EXIT_COMMAND) {
flushNewTuplesToMainStorage()
previousPollEndedWithFlush = true
return jsonString == EXIT_COMMAND
}
previousPollEndedWithFlush = false
parseJson(jsonString)
return false
}
private fun parseJson(jsonString: String) {
val currCallInfo = ben(jsonTime) {
try {
return@ben gson.fromJson(jsonString, ServerResponseBean::class.java)?.toCallInfo()
} catch (ex: Throwable) {
when (ex) {
is JsonSyntaxException, is JsonParseException -> {
// Sometimes it's possible that some json fields contain quotation mark and we got JsonSyntaxException
LOGGER.severe("Cannot parse: $jsonString")
}
is IllegalStateException -> {
LOGGER.severe(ex.message)
}
else -> throw ex
}
return@ben null
}
}
// filter, for example, such things #<Class:DidYouMean::Jaro>
if (currCallInfo?.methodInfo?.classInfo?.classFQN?.startsWith("#<") == true) {
return
}
if (currCallInfo != null) {
ben(addTime) { callInfoContainer.add(currCallInfo) }
}
}
private fun flushNewTuplesToMainStorage() {
DatabaseProvider.defaultDatabaseTransaction {
for (callInfo in callInfoContainer) {
CallInfoTable.insertInfoIfNotContains(callInfo)
}
}
callInfoContainer.clear()
afterFlushListener?.invoke()
}
private inner class SignatureHandler internal constructor() : Thread() {
var pipeFilePath: String = ""
override fun run() {
try {
var missed = 0
var br = FileInputStream(pipeFilePath).bufferedReader()
var currString: String? = ""
do {
// continue when EOF is reached because EOF doesn't mean that program
// traced by arg-scanner is died. Program could simply call `Kernel.exec`
// See CallStatCompletionTest.testRubyExecWithBuffering and
// CallStatCompletionTest.testRubyExecWithoutBuffering
currString = ben(readTime) { br.readLine() }
if (currString != null) {
queue.put(currString)
} else {
missed++
br.close()
// If don't reassign reader then `readLine` will always return `null`
br = FileInputStream(pipeFilePath).bufferedReader()
}
// 1000 is just threshold for safety
} while (currString != EXIT_COMMAND && missed < 1000)
} catch (e: IOException) {
LOGGER.severe("Error in SignatureHandler")
} finally {
File(pipeFilePath).delete()
}
}
}
private inner class PollJsonThread : Thread() {
override fun run() {
while (true) {
if (pollJson()) {
isReady.set(true)
afterExitListener?.invoke()
_runningServers.remove(this@SignatureServer)
break
}
}
}
}
}
fun <T> ben(x: AtomicLong, F: ()->T): T {
val start = System.nanoTime()
try {
return F.invoke()
}
finally {
x.addAndGet(System.nanoTime() - start)
}
}
================================================
FILE: contract-creator/src/org/jetbrains/ruby/runtime/signature/server/SignatureServerInjector.kt
================================================
package org.jetbrains.ruby.runtime.signature.server
import org.jetbrains.ruby.codeInsight.Injector
import org.jetbrains.ruby.codeInsight.Logger
import org.jetbrains.ruby.codeInsight.PrintToStdoutLogger
object SignatureServerInjector : Injector {
override fun <T> getLogger(cl: Class<T>): Logger {
return PrintToStdoutLogger(cl)
}
}
================================================
FILE: contract-creator/src/org/jetbrains/ruby/runtime/signature/server/serialisation/ServerResponseBean.kt
================================================
package org.jetbrains.ruby.runtime.signature.server.serialisation
import org.jetbrains.ruby.codeInsight.types.signature.*
data class ServerResponseBean(
val method_name: String,
/**
* Number of unnamedArguments passed by user explicitly
*
* For example for method:
* def foo(a, b = 1); end
*
* This method invocation have only one explicit argument
* foo(4)
*
* But this method invocation have two explicit unnamedArguments
* foo(4, 5)
*/
val call_info_argc: Int,
val args_info: String,
val visibility: String,
val path: String,
val lineno: Int,
val receiver_name: String,
val return_type_name: String)
// explicit here means that this unnamedArguments was explicitly provided by user
// for example:
// def foo(a, b = 1); end
// foo(1) # here only `a` is explicitly provided
// foo(1, 5) # here `a` and `b` are both explicitly provided
private data class Arg(val paramInfo: ParameterInfo, val type: String, var explicit: Boolean)
private const val PARAMETER_MODIFIER_INDEX_IN_ATTRIBUTES = 0
private const val PARAMETER_TYPE_INDEX_IN_ATTRIBUTES = 1
private const val PARAMETER_NAME_INDEX_IN_ATTRIBUTES = 2
private const val NUMBER_OF_ATTRIBUTES_FOR_PARAMETER = 3
/**
* @throws IllegalStateException if [ServerResponseBean] is not correctly formed
*/
fun ServerResponseBean.toCallInfo(): CallInfo {
var argc = this.call_info_argc
val args = this.args_info.takeIf { it != "" }?.split(";")?.map {
val parts: List<String> = it.split(",")
val modifier = parts[PARAMETER_MODIFIER_INDEX_IN_ATTRIBUTES]
val type = parts[PARAMETER_TYPE_INDEX_IN_ATTRIBUTES]
val name = if (parts.size == NUMBER_OF_ATTRIBUTES_FOR_PARAMETER) {
// It's possible that parameter in ruby doesn't have name, for example:
// def foo(*); end
parts[PARAMETER_NAME_INDEX_IN_ATTRIBUTES]
} else {
""
}
// If argc == -1 then all args are explicitly passed
return@map Arg(ParameterInfo(name, ParameterInfo.Type.valueOf(modifier)), type, explicit = argc == -1)
} ?: emptyList()
if (argc != -1) {
for (arg in args) {
if (arg.paramInfo.isNamedParameter ||
arg.paramInfo.modifier == ParameterInfo.Type.REQ ||
arg.paramInfo.modifier == ParameterInfo.Type.POST) {
arg.explicit = true
argc--
}
}
for (arg in args) {
if (argc <= 0) {
break
}
if (arg.paramInfo.modifier == ParameterInfo.Type.OPT) {
arg.explicit = true
argc--
}
}
for (arg in args) {
if (argc <= 0) {
break
}
if (arg.paramInfo.modifier == ParameterInfo.Type.REST) {
arg.explicit = true
argc--
}
}
check(argc == 0 || args.any { it.paramInfo.modifier == ParameterInfo.Type.BLOCK } && argc == 1) {
"Failed to parse this bean: ${this.toString()}"
}
}
val namedArgumentsNamesToTypes = args.asSequence().filter { it.paramInfo.isNamedParameter }
.map { ArgumentNameAndType(it.paramInfo.name, it.type) }.toList()
val unnamedArgumentsTypes = args.asSequence().filter { !it.paramInfo.isNamedParameter }
.map { arg ->
ArgumentNameAndType(arg.paramInfo.name, arg.type.takeIf { arg.explicit }
?: ArgumentNameAndType.IMPLICITLY_PASSED_ARGUMENT_TYPE)
}.toList()
val methodInfo = MethodInfo.Impl(
ClassInfo.Impl(gemInfoFromFilePathOrNull(this.path), this.receiver_name),
this.method_name,
RVisibility.valueOf(this.visibility),
Location(this.path, this.lineno))
return CallInfoImpl(methodInfo, namedArgumentsNamesToTypes, unnamedArgumentsTypes, this.return_type_name)
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Nov 07 19:25:40 MSK 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
================================================
FILE: gradle.properties
================================================
# Available idea versions:
# https://www.jetbrains.com/intellij-repository/releases
# https://www.jetbrains.com/intellij-repository/snapshots
# ruby plugin versions can be found here:
# https://plugins.jetbrains.com/plugin/1293-ruby/versions
kotlin_version=1.2.70
ideaVersion=IU-193.5233.102
rubyPluginVersion=193.5233.57
exposedVersion=0.17.3
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: ide-plugin/CHANGELOG.md
================================================
## 0.1.1 (15 Dec 2017)
* (#17) Fix "find usages" action for dynamic symbols which resolve to text-based
definitions.
## 0.1 (29 Nov 2017)
Initial plugin version
* Collect State action
Adds on_exit hook which dumps class/module includes structure and contained methods
which can be used for resolution/completion later.
* Collect Type action
Enables call tracing (with a considerable slowdown) and dumps return types which
can be used for better type inference.
* Symbol/Type provider to improve resolution and type inference based on the collected
data.
================================================
FILE: ide-plugin/build.gradle
================================================
buildscript {
repositories {
maven { url 'https://dl.bintray.com/jetbrains/intellij-plugin-service' }
}
}
plugins {
id "org.jetbrains.intellij" version "0.3.11"
}
dependencies {
def withoutKotlinAndMySql = {
exclude group: 'org.jetbrains.kotlin'
exclude group: 'mysql'
}
def withoutSlfAndKotlinAndMySql = {
exclude group: 'org.slf4j'//, module: 'slf4j-api'
exclude group: 'org.jetbrains.kotlin'
exclude group: 'mysql'
}
compile project(':common')
compile project(':ruby-call-signature'), withoutKotlinAndMySql
compile project(':storage-server-api'), withoutSlfAndKotlinAndMySql
compile project(':contract-creator'), withoutSlfAndKotlinAndMySql
compile project(':state-tracker'), withoutSlfAndKotlinAndMySql
// https://mvnrepository.com/artifact/com.h2database/h2
compile group: 'com.h2database', name: 'h2', version: '1.4.199'
}
sourceSets {
main.java.srcDirs = ['src']
main.resources.srcDirs = ['resources']
test.java.srcDirs = ['src/test/java']
test.resources.srcDirs=['src/test/testData']
}
intellij {
version ideaVersion
pluginName 'ruby-runtime-stats'
plugins = ["yaml", "org.jetbrains.plugins.ruby:$rubyPluginVersion"]
}
patchPluginXml {
sinceBuild '193.5233.102'
untilBuild '193.*'
version '0.3.3'
}
prepareSandbox.doLast {
def destDir = "$it.destinationDir/$intellij.pluginName"
copy {
from sourceSets.main.resources.include("**/*.rb", "**/*.db")
into destDir
}
}
test {
testLogging.showStandardStreams = true
}
================================================
FILE: ide-plugin/resources/META-INF/plugin.xml
================================================
<idea-plugin>
<id>org.jetbrains.ruby-runtime-stats</id>
<name>Ruby Dynamic Code Insight</name>
<vendor email="" url="http://www.jetbrains.com">JetBrains</vendor>
<depends>com.intellij.modules.ruby</depends>
<description><![CDATA[
<p>This plugin provides additional Code Insight intelligence to improve resolution, find usages and refactoring
capabilities.</p>
<p>The data is obtained via user project execution altered by a special tracker which stores symbol
hierarchy, method return types, etc.</p>
]]></description>
<change-notes><![CDATA[
<a href="https://github.com/JetBrains/ruby-type-inference/blob/master/ide-plugin/CHANGELOG.md">Changelog</a>
]]>
</change-notes>
<applicationListeners>
<listener class="org.jetbrains.plugins.ruby.ruby.codeInsight.ProjectLifecycleListenerImpl"
topic="com.intellij.openapi.project.ProjectManagerListener"/>
<listener class="org.jetbrains.plugins.ruby.ruby.codeInsight.RubyDynamicCodeInsightPluginAppLifecyctlListener"
topic="com.intellij.ide.AppLifecycleListener"
activeInHeadlessMode="true"/>
</applicationListeners>
<extensions defaultExtensionNs="com.intellij">
<executor implementation="com.intellij.execution.executors.RunWithTypeTrackerExecutor"/>
<!--<executor implementation="com.intellij.execution.executors.CollectStateExecutor"/>-->
<programRunner implementation="org.jetbrains.plugins.ruby.ruby.codeInsight.types.RubyRunWithTypeTrackerRunner"/>
<programRunner implementation="org.jetbrains.plugins.ruby.ruby.codeInsight.types.RubyCollectStateRunner"/>
<projectService serviceImplementation="org.jetbrains.plugins.ruby.settings.RubyTypeContractsSettings"/>
<intentionAction>
<className>org.jetbrains.plugins.ruby.ruby.intentions.AddContractAnnotationIntention</className>
</intentionAction>
<intentionAction>
<className>org.jetbrains.plugins.ruby.ruby.intentions.RemoveCollectedInfoIntention</className>
</intentionAction>
<postStartupActivity implementation="org.jetbrains.plugins.ruby.ruby.codeInsight.TrackerDataLoader"/>
<applicationConfigurable groupId="language"
groupWeight="130"
instance="org.jetbrains.plugins.ruby.settings.RubyTypeContractsConfigurable"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.plugins.ruby">
<rubyTypeProvider implementation="org.jetbrains.plugins.ruby.ruby.codeInsight.types.RubyParameterTypeProvider"/>
<symbolicTypeInferenceProvider implementation="org.jetbrains.plugins.ruby.ruby.codeInsight.types.ReturnTypeSymbolicTypeInferenceProvider"/>
<symbolProvider implementation="org.jetbrains.plugins.ruby.ruby.codeInsight.stateTracker.ClassHierarchySymbolProvider"/>
<runConfigurationExtension
implementation="org.jetbrains.plugins.ruby.ruby.run.configuration.RunWithTypeTrackerRunConfigurationExtension"/>
</extensions>
<actions>
<group id="ruby.contracts.group"
text="Type Contracts"
popup="true">
<add-to-group group-id="RUBY_TOOLS" anchor="before" relative-to-action="BUNDLER_ACTIONS"/>
<action class="org.jetbrains.plugins.ruby.ruby.actions.ExportContractsAction"
id="ruby.contracts.export"
text="Export type contracts"/>
<action class="org.jetbrains.plugins.ruby.ruby.actions.ImportContractsAction"
id="ruby.contracts.import"
text="Import type contracts"/>
</group>
<group id="ruby.ancestors_extractor.group"
text="Export ancestors (for rails applications only!)"
popup="true" internal="true">
<add-to-group group-id="RUBY_TOOLS" anchor="before" relative-to-action="BUNDLER_ACTIONS"/>
<action class="org.jetbrains.plugins.ruby.ruby.actions.ExportAncestorsByObjectSpaceAction"
id="ruby.ancestors_extractor.export_by_objectspace"
text="Export ancestors by Ruby's objectspace"/>
<action class="org.jetbrains.plugins.ruby.ruby.actions.ExportAncestorsByRubymineAction"
id="ruby.ancestors_extractor.export_by_rubymine"
text="Export ancestors by Rubymine"/>
<action class="org.jetbrains.plugins.ruby.ruby.actions.ExportAncestorsDiffAction"
id="ruby.ancestors_extractor.diff"
text="Export ancestors diff"/>
</group>
</actions>
</idea-plugin>
================================================
FILE: ide-plugin/src/com/intellij/execution/executors/CollectStateExecutor.kt
================================================
package com.intellij.execution.executors
import com.intellij.execution.Executor
import com.intellij.icons.AllIcons
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.wm.ToolWindowId
import javax.swing.Icon
class CollectStateExecutor : Executor() {
private val myIcon = AllIcons.General.GearPlain
override fun getToolWindowId(): String {
return ToolWindowId.RUN
}
override fun getToolWindowIcon(): Icon {
return myIcon
}
override fun getIcon(): Icon {
return myIcon
}
override fun getDisabledIcon(): Icon? {
return null
}
override fun getDescription(): String {
return "Run selected configuration with collecting state"
}
override fun getActionName(): String {
return "CollectState"
}
override fun getId(): String {
return EXECUTOR_ID
}
override fun getStartActionText(): String {
return "Run with Collecting State"
}
override fun getContextActionId(): String {
return "RunCollectState"
}
override fun getHelpId(): String? {
return null
}
override fun getStartActionText(configurationName: String): String {
val name = escapeMnemonicsInConfigurationName(
StringUtil.first(configurationName, 30, true))
return "Run" + (if (StringUtil.isEmpty(name)) "" else " '$name'") + " with Collecting State"
}
private fun escapeMnemonicsInConfigurationName(configurationName: String): String {
return configurationName.replace("_", "__")
}
companion object {
val EXECUTOR_ID = "CollectState"
}
}
================================================
FILE: ide-plugin/src/com/intellij/execution/executors/RunWithTypeTrackerExecutor.java
================================================
package com.intellij.execution.executors;
import com.intellij.execution.Executor;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.net.URL;
public class RunWithTypeTrackerExecutor extends Executor {
@NotNull
public static final String EXECUTOR_ID = "RunWithTypeTracker";
@NotNull
private final Icon myIcon;
public RunWithTypeTrackerExecutor() {
final URL iconURL = RunWithTypeTrackerExecutor.class.getClassLoader().getResource(
UIUtil.isUnderDarcula() ? "runWithTypeTracker_dark.svg" : "runWithTypeTracker.svg");
final Icon icon = IconLoader.findIcon(iconURL);
myIcon = icon != null ? icon : AllIcons.General.Error;
}
@Override
public String getToolWindowId() {
return ToolWindowId.RUN;
}
@Override
public Icon getToolWindowIcon() {
return myIcon;
}
@NotNull
@Override
public Icon getIcon() {
return myIcon;
}
@Nullable
@Override
public Icon getDisabledIcon() {
return null;
}
@NotNull
@Override
public String getDescription() {
return "Run selected configuration with Type Tracker";
}
@NotNull
@Override
public String getActionName() {
return "Run with Type Tracker";
}
@NotNull
@Override
public String getId() {
return EXECUTOR_ID;
}
@NotNull
public String getStartActionText() {
return "Run with Type Tracker";
}
@NotNull
@Override
public String getContextActionId() {
return "Run with Type Tracker";
}
@Nullable
@Override
public String getHelpId() {
return null;
}
@NotNull
@Override
public String getStartActionText(@NotNull final String configurationName) {
final String name = escapeMnemonicsInConfigurationName(
StringUtil.first(configurationName, 30, true));
return "Run" + (StringUtil.isEmpty(name) ? "" : " '" + name + "'") + " with Type Tracker";
}
@NotNull
private static String escapeMnemonicsInConfigurationName(@NotNull final String configurationName) {
return configurationName.replace("_", "__");
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/IdePluginLogger.kt
================================================
package org.jetbrains.plugins.ruby
import org.jetbrains.ruby.codeInsight.Logger
class IdePluginLogger(private val intellijPlatformLogger: com.intellij.openapi.diagnostic.Logger) : Logger {
override fun info(msg: String) {
intellijPlatformLogger.info(msg)
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/PluginResourceUtil.java
================================================
package org.jetbrains.plugins.ruby;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.openapi.extensions.PluginId;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public final class PluginResourceUtil {
private static final String PLUGIN_ID = "org.jetbrains.ruby-runtime-stats";
private PluginResourceUtil() {
}
@NotNull
public static String getPluginResourcesPath() {
final IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId(PLUGIN_ID));
if (plugin == null) {
throw new AssertionError("Nonsense: this plugin is not registered");
}
final File pluginHome = plugin.getPath();
if (pluginHome == null) {
throw new AssertionError("Corrupted plugin: could not find home");
}
return pluginHome.getPath() + "/";
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/RubyDynamicCodeInsightPluginInjector.kt
================================================
package org.jetbrains.plugins.ruby
import org.jetbrains.ruby.codeInsight.Injector
import org.jetbrains.ruby.codeInsight.Logger
object RubyDynamicCodeInsightPluginInjector : Injector {
override fun <T> getLogger(cl: Class<T>): Logger {
return IdePluginLogger(com.intellij.openapi.diagnostic.Logger.getInstance(cl))
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ancestorsextractor/AncestorsExtractor.kt
================================================
package org.jetbrains.plugins.ruby.ancestorsextractor
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.ThrowableComputable
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.SymbolUtil
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.util.SymbolHierarchy
import java.io.FileWriter
import java.io.PrintWriter
/**
* Keeps Ruby module [name] and it's [ancestors]
*/
data class RubyModule(val name: String, val ancestors: List<String>)
/**
* Ancestors extractor for Ruby's project's modules
*/
interface AncestorsExtractorBase {
/**
* Extract ancestors for every Ruby's Module in [project]
*/
fun extractAncestors(project: Project, sdk: Sdk): List<RubyModule>
/**
* Set [RailsConsoleRunner.Listener] for [RailsConsoleRunner]
* @see RailsConsoleRunner.Listener
*/
var listener: RailsConsoleRunner.Listener?
}
/**
* Extract ancestors for Ruby's modules the way how RubyMine sees them.
* If you don't provide [allModulesNames] this implementation works only for Ruby on Rails project as in case when
* you don't provide [allModulesNames] all modules names would be taken from Ruby on Rails console ("bin/rails console")
*/
class AncestorsExtractorByRubyMine(private val allModulesNames: List<String>? = null,
override var listener: RailsConsoleRunner.Listener? = null) : AncestorsExtractorBase {
/**
* Implementation of [AncestorsExtractorBase.extractAncestors] based on how RubyMine sees ancestors
*/
override fun extractAncestors(project: Project, sdk: Sdk): List<RubyModule> {
val allModulesNamesLocal: List<String> = allModulesNames ?: extractAllModulesNames(project, sdk)
// I don't know why but seems that SymbolScopeUtil#getAncestorsCaching needs to be called
// inside ReadAction otherwise sometimes I get Exception
return ReadAction.compute(ThrowableComputable<List<RubyModule>, Exception> {
allModulesNamesLocal.map { RubyModule(it, extractAncestorsFor(it, project)) }
})
}
/**
* Extract all modules names from Ruby on Rails project.
*/
private fun extractAllModulesNames(project: Project, sdk: Sdk): List<String> {
val tempFile = createTempFile(prefix = "modules", suffix = ".json")
try {
val rubyCode = """
require 'json'
open("${tempFile.path}", "w") do |f|
f.puts JSON.generate(ObjectSpace.each_object(Module).to_a.map {|from| from.to_s})
end
""".trimIndent()
return RailsConsoleRunner(listener).extractFromRubyOnRailsConsole(project, sdk, Array<String>::class.java,
tempFile.path, rubyCode, eagerLoad = true).toList()
} finally {
tempFile.delete()
}
}
private fun extractAncestorsFor(rubyModuleName: String, project: Project): List<String> {
return SymbolUtil.findConstantByFQN(project, rubyModuleName)?.let {
SymbolHierarchy.getAncestorsCaching(it, null)
}?.map { it.symbol.fqnWithNesting.toString() } ?: listOf()
}
}
/**
* Extract ancestors the way Ruby's Module.ancestors method does this.
* This implementation works only for Ruby on Rails project.
* @see <a href="https://ruby-doc.org/core-2.1.0/Module.html#method-i-ancestors">Ruby's Module.ancestors</a>
*/
class AncestorsExtractorByObjectSpace(override var listener: RailsConsoleRunner.Listener? = null) : AncestorsExtractorBase {
/**
* Implementation of [AncestorsExtractorBase.extractAncestors] based on Ruby's Module.ancestors method
* @see <a href="https://ruby-doc.org/core-2.1.0/Module.html#method-i-ancestors">Ruby's Module.ancestors</a>
*/
override fun extractAncestors(project: Project, sdk: Sdk): List<RubyModule> {
val tempFile = createTempFile(prefix = "module-ancestors-pair", suffix = ".json")
try {
val rubyCode = """
objects = ObjectSpace.each_object(Module).to_a; nil # nil is to prevent irb to print big objects output
objects = objects.map {|mod| {:name => mod.to_s, :ancestors => mod.ancestors.map {|from| from.to_s}}}; nil
require 'json'
open("${tempFile.path}", "w") do |f|
f.puts JSON.generate(objects)
end
""".trimIndent()
return RailsConsoleRunner(listener).extractFromRubyOnRailsConsole(project, sdk, Array<RubyModule>::class.java,
tempFile.path, rubyCode, eagerLoad = true).toList()
} finally {
tempFile.delete()
}
}
/**
* Extract information about where ruby includes was performed
* @return Map where key is [String] with the following format: "**ancestor**#**includer**" and value is [String]
* containing ruby file path and line number where **ancestor** was included by **includer**
*/
fun extractIncludes(project: Project, sdk: Sdk): Map<String, String> {
val tempWhereIncluded = createTempFile(prefix = "where-included", suffix = ".json")
val tempRubyCodeFile = createTempFile(prefix = "preload-temp-script", suffix = ".rb")
try {
PrintWriter(FileWriter(tempRubyCodeFile)).use {
it.println("""
END {
require 'json'
open("${tempWhereIncluded.path}", "w") { |f| f.puts JSON.generate(RubyDetectIncludeUniqueModuleName.get) }
}
module RubyDetectIncludeUniqueModuleName
@@map = {}
def self.get
return @@map
end
def append_features(mod)
# self included by mod
@@map["#{self.to_s}##{mod.to_s}"] = caller_locations()[1].to_s
super
end
end
Module.prepend RubyDetectIncludeUniqueModuleName
""".trimIndent())
}
@Suppress("UNCHECKED_CAST")
return RailsConsoleRunner(listener).extractFromRubyOnRailsConsole(project, sdk, Map::class.java,
tempWhereIncluded.path, rubyCode = "", eagerLoad = true,
rubyConsoleArguments = arrayOf("-r", tempRubyCodeFile.path)) as Map<String, String>
} finally {
tempRubyCodeFile.delete()
tempWhereIncluded.delete()
}
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ancestorsextractor/RailsConsoleRunner.kt
================================================
package org.jetbrains.plugins.ruby.ancestorsextractor
import com.google.gson.Gson
import com.intellij.execution.ExecutionException
import com.intellij.execution.ExecutionModes
import com.intellij.execution.process.ProcessEvent
import com.intellij.execution.process.ProcessListener
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.ThrowableComputable
import org.jetbrains.plugins.ruby.ancestorsextractor.RailsConsoleRunner.Listener
import org.jetbrains.plugins.ruby.rails.Rails3Constants
import org.jetbrains.plugins.ruby.rails.Rails4Constants
import org.jetbrains.plugins.ruby.ruby.RubyUtil
import org.jetbrains.plugins.ruby.ruby.run.context.RubyScriptExecutionContext
import java.io.File
import java.io.IOException
import java.io.PrintWriter
import java.nio.file.Paths
/**
* Runs some Ruby code on Ruby on Rails console ("bin/rails console")
*/
class RailsConsoleRunner(
/**
* Set [Listener]. There is only one possible [Listener]. Feel free to change it to
* addListener to have multiple listeners if you want
*/
private var listener: RailsConsoleRunner.Listener?) {
data class RailsConsoleExecutionResult(val stdout: String, val stderr: String)
/**
* Extract information left by [rubyCode] in [tempJSONFilPath]
* @param clazz which kind of information [rubyCode] left in [tempJSONFilPath].
* Note: Do not use [List] here because [Gson] won't parse it, use [Array] instead
* @param tempJSONFilPath path to temp file where [rubyCode] left information which
* can be converted from JSON to [clazz]
* @param rubyCode Your ruby code which should leave some JSON information in [tempJSONFilPath]
* @param eagerLoad Works like you set `eager_load` variable inside config/environments/LOADED_ENVIRONMENT.rb
* @param rubyConsoleArguments additional arguments to pass to ruby interpreter
* @throws ExecutionException when error occurred either while executing [rubyCode] either
* while trying to read data from JSON left by Ruby
* @throws IllegalStateException when getter [Project.getBasePath] of [project] returns `null`
*/
@Throws(ExecutionException::class, IllegalStateException::class)
fun <T> extractFromRubyOnRailsConsole(project: Project, sdk: Sdk, clazz: Class<T>, tempJSONFilPath: String,
rubyCode: String, eagerLoad: Boolean,
rubyConsoleArguments: Array<String> = arrayOf()): T {
val projectDirPath = project.basePath ?: throw IllegalStateException("Seems that project is default. " +
"Quote from com.intellij.openapi.project.Project#getBasePath JavaDoc")
val rubyCodeToExec = if (eagerLoad) {
"""
Rails.application.eager_load!; nil # nil is to prevent irb to print big output
""".trimIndent() + rubyCode
} else {
rubyCode
}
val railsConsoleExecutionResult = runRailsConsole(projectDirPath, sdk,
rubyCodeToExec, rubyConsoleArguments, railsConsoleArguments = arrayOf("--environment=development"))
val ret = ReadAction.compute(ThrowableComputable<T?, Exception> {
val file = File(tempJSONFilPath)
return@ThrowableComputable try {
file.inputStream().bufferedReader().use {
Gson().fromJson(it.readLine(), clazz)
}
} catch (ex: IOException) {
null
}
})
listener?.informationWasExtractedFromIRB()
return ret ?: throw ExecutionException("""
|Error occurred either in the following Ruby code (ruby was launched with these arguments: ${rubyConsoleArguments.contentToString()}):
|$rubyCodeToExec
|stdout of this Ruby code execution:
|${railsConsoleExecutionResult.stdout}
|stderr of this Ruby code execution:
|${railsConsoleExecutionResult.stderr}
|either while trying to read data from JSON left by Ruby
""".trimMargin())
}
/**
* Run [toExec] in ruby on rails console ("bin/rails console"). You can use it for example for generating
* some temp json files to later parse them in Kotlin/Java
* @param projectDirPath Path to project dir
* @param toExec Newline separated [String] to execute in ruby on rails console
* @param rubyConsoleArguments additional arguments to pass to ruby interpreter
* @param railsConsoleArguments additional arguments to pass to "bin/rails console"
* @throws ExecutionException when error occurred while launching rails console
*/
@Throws(ExecutionException::class)
fun runRailsConsole(projectDirPath: String, sdk: Sdk, toExec: String,
rubyConsoleArguments: Array<String> = arrayOf(),
railsConsoleArguments: Array<String> = arrayOf()): RailsConsoleExecutionResult {
val executionMode = ExecutionModes.SameThreadMode(false)
executionMode.addProcessListener(object : ProcessListener {
override fun processTerminated(event: ProcessEvent) { }
override fun processWillTerminate(event: ProcessEvent, willBeDestroyed: Boolean) { }
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { }
override fun startNotified(event: ProcessEvent) {
PrintWriter(event.processHandler.processInput, true).use { it.println(toExec); it.println("quit") }
}
})
val processOutput = RubyScriptExecutionContext.create(Paths.get(projectDirPath, Rails4Constants.CONSOLE4_SCRIPT).toString(), sdk)
// .withInterpreterOptions(*rubyConsoleArguments) todo API doesn't exist anymore :(
// todo replace with .withInterpreterOptions when API becomes available
.withAdditionalEnvs(mapOf(RubyUtil.RUBYOPT to rubyConsoleArguments.joinToString(separator = " ")))
.withArguments(Rails3Constants.CONSOLE, *railsConsoleArguments)
.withExecutionMode(executionMode)
.withWorkingDirPath(projectDirPath).executeScript()
?: throw ExecutionException("Error occurred while launching rails console")
listener?.irbConsoleExecuted()
return RailsConsoleRunner.RailsConsoleExecutionResult(
processOutput.stdout,
processOutput.stderr
)
}
/**
* [Listener] of particular events in [RailsConsoleRunner].
*/
interface Listener {
/**
* It would be called first
*/
fun irbConsoleExecuted()
/**
* It would be called second
*/
fun informationWasExtractedFromIRB()
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/actions/ExportAncestorsActions.kt
================================================
package org.jetbrains.plugins.ruby.ruby.actions
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import org.jetbrains.plugins.ruby.ancestorsextractor.AncestorsExtractorBase
import org.jetbrains.plugins.ruby.ancestorsextractor.AncestorsExtractorByObjectSpace
import org.jetbrains.plugins.ruby.ancestorsextractor.AncestorsExtractorByRubyMine
import org.jetbrains.plugins.ruby.ancestorsextractor.RubyModule
import java.io.PrintWriter
/**
* Base class representation for exporting Ruby on Rails project's modules' ancestors
*/
abstract class ExportAncestorsActionBase(
whatToExport: String,
generateFilename: (Project) -> String,
private val extractor: AncestorsExtractorBase
) : ExportFileActionBase(whatToExport, generateFilename, extensions = arrayOf("txt"),
numberOfProgressBarFractions = 5) {
override fun backgroundProcess(absoluteFilePath: String, module: Module?, sdk: Sdk?, project: Project) {
moveProgressBarForward()
extractor.listener = ProgressListener()
val ancestors: List<RubyModule> = try {
extractor.extractAncestors(project, sdk ?: throw IllegalStateException("Ruby SDK is not set"))
} catch(ex: Throwable) {
PrintWriter(absoluteFilePath).use {
it.println(ex.message)
}
return
}
moveProgressBarForward()
PrintWriter(absoluteFilePath).use { printWriter ->
ancestors.forEach {
printWriter.println("Module: ${it.name}")
printWriter.print("Ancestors: ")
if (it.ancestors.isEmpty()) printWriter.print("Nothing found")
it.ancestors.forEach { printWriter.print("$it ") }
printWriter.print("\n\n")
}
}
moveProgressBarForward()
}
}
class ExportAncestorsByObjectSpaceAction : ExportAncestorsActionBase(
whatToExport = "ancestors by ObjectSpace",
generateFilename = { project -> "ancestors-by-objectspace-${project.name}" },
extractor = AncestorsExtractorByObjectSpace()
)
class ExportAncestorsByRubymineAction : ExportAncestorsActionBase(
whatToExport = "ancestors by RubyMine",
generateFilename = { project -> "ancestors-by-rubymine-${project.name}" },
extractor = AncestorsExtractorByRubyMine()
)
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/actions/ExportAncesttorsDiffAction.kt
================================================
package org.jetbrains.plugins.ruby.ruby.actions
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import org.jetbrains.plugins.ruby.ancestorsextractor.AncestorsExtractorByObjectSpace
import org.jetbrains.plugins.ruby.ancestorsextractor.AncestorsExtractorByRubyMine
import org.jetbrains.plugins.ruby.ancestorsextractor.RubyModule
import java.io.PrintWriter
class ExportAncestorsDiffAction : ExportFileActionBase(whatToExport = "ancestors diff",
generateFilename = { project: Project -> "ancestors-diff-${project.name}" }, extensions = arrayOf("txt"),
numberOfProgressBarFractions = 9) {
override fun backgroundProcess(absoluteFilePath: String, module: Module?, sdk: Sdk?, project: Project) {
moveProgressBarForward()
val byObjectSpace: List<RubyModule>
val byRubyMine: List<RubyModule>
val ancestorHashSymbolIncluderToWhereIncluded: Map<String, String>
try {
val listener = ProgressListener()
val ancestorsExtractorByObjectSpace = AncestorsExtractorByObjectSpace(listener)
// Here all listener methods would be called
byObjectSpace = ancestorsExtractorByObjectSpace.extractAncestors(project, sdk ?: throw IllegalStateException("Ruby SDK is not set"))
// The second place where all listener methods would be called
ancestorHashSymbolIncluderToWhereIncluded = ancestorsExtractorByObjectSpace.extractIncludes(project, sdk)
// Provide all modulesNames same as in byObjectSpace for easy ancestors comparison
val allModulesNames: List<String> = byObjectSpace.map { it.name }
// The third place where all listener methods would be called
byRubyMine = AncestorsExtractorByRubyMine(allModulesNames, listener)
.extractAncestors(project, sdk)
} catch (ex: Throwable) {
PrintWriter(absoluteFilePath).use {
it.println(ex.message)
}
return
}
moveProgressBarForward()
PrintWriter(absoluteFilePath).use { printWriter ->
val objectSpaceIterator = byObjectSpace.iterator()
val rubymineIterator = byRubyMine.iterator()
while (objectSpaceIterator.hasNext() && rubymineIterator.hasNext()) {
val a = objectSpaceIterator.next()
val b = rubymineIterator.next()
assert(a.name == b.name)
printWriter.println("Module: ${a.name}")
var same = true
a.ancestors.filter { !b.ancestors.contains(it) }.let {
if (!it.isEmpty()) {
same = false
printWriter.print("Ancestors in ObjectSpace only: ")
it.forEach {
val whereIncluded = ancestorHashSymbolIncluderToWhereIncluded[it + "#" + a.name]
var toPrint = it
if (whereIncluded != null) {
toPrint += "($whereIncluded)"
}
printWriter.print("$toPrint ")
}
printWriter.println()
}
}
b.ancestors.filter { !a.ancestors.contains(it) }.let {
if (!it.isEmpty()) {
same = false
printWriter.print("Ancestors in RubyMine only: ")
it.forEach { printWriter.print("$it ") }
printWriter.println()
}
}
if (same) {
printWriter.println("No difference in ancestors list")
}
printWriter.println()
}
}
moveProgressBarForward()
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/actions/ExportFileActionBase.kt
================================================
package org.jetbrains.plugins.ruby.ruby.actions
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.fileChooser.FileSaverDescriptor
import com.intellij.openapi.fileChooser.ex.FileChooserDialogImpl
import com.intellij.openapi.fileChooser.ex.FileSaverDialogImpl
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.util.ProgressWindow
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.DialogBuilder
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.ThrowableComputable
import org.jetbrains.plugins.ruby.ancestorsextractor.AncestorsExtractorBase
import org.jetbrains.plugins.ruby.ancestorsextractor.RailsConsoleRunner
import org.jetbrains.plugins.ruby.ruby.RModuleUtil
/**
* Base class representing file export action with "save to" dialog
* @param whatToExport Will be shown in "save to" dialog
* @param generateFilename Generate filename for "save to" dialog
* @param extensions Array of available extensions for exported file
* @param description Description in "save to" dialog
*/
abstract class ExportFileActionBase(
private val whatToExport: String,
private val generateFilename: (Project) -> String,
private val extensions: Array<String>,
private val description: String = "",
private val numberOfProgressBarFractions: Int? = null
) : DumbAwareAction() {
final override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val dialog = FileSaverDialogImpl(FileSaverDescriptor(
"Export $whatToExport",
description,
*extensions), project)
val fileWrapper = dialog.save(null, generateFilename(project)) ?: return
val module: Module? = RModuleUtil.getInstance().getModule(e.dataContext)
val sdk: Sdk? = RModuleUtil.getInstance().findRubySdkForModule(module)
try {
ProgressManager.getInstance().runProcessWithProgressSynchronously(ThrowableComputable<Unit, Exception> {
return@ThrowableComputable backgroundProcess(fileWrapper.file.absolutePath, module, sdk, project)
}, "Exporting $whatToExport", false, project)
} catch (ex: Exception) {
Messages.showErrorDialog(ex.message, "Error while exporting $whatToExport")
}
}
/**
* In this method implementation you can do you job needed for file export and then file exporting itself.
*
* @param absoluteFilePath absolute file path which user have chosen to save file to.
* @param module module from the context of action it invoked
* @param sdk sdk from the context of action it invoked
* @param project project from the context of action it invoked
*/
protected abstract fun backgroundProcess(absoluteFilePath: String, module: Module?, sdk: Sdk?, project: Project)
@Throws(IllegalStateException::class)
protected fun moveProgressBarForward() {
if (numberOfProgressBarFractions == null) throw IllegalStateException("You cannot call moveProgressBarForward() " +
"method when progressBarFractions property is null")
val progressIndicator = ProgressManager.getInstance().progressIndicator
if (progressIndicator is ProgressWindow) {
progressIndicator.fraction = minOf(1.0, progressIndicator.fraction + 1.0/numberOfProgressBarFractions)
}
}
/**
* You can use to set as [AncestorsExtractorBase.listener] because every [ProgressListener]
* method call just calls [moveProgressBarForward]
*/
protected inner class ProgressListener : RailsConsoleRunner.Listener {
override fun irbConsoleExecuted() {
moveProgressBarForward()
}
override fun informationWasExtractedFromIRB() {
moveProgressBarForward()
}
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/actions/ImportExportContractsAction.kt
================================================
package org.jetbrains.plugins.ruby.ruby.actions
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileChooser.ex.FileChooserDialogImpl
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.ThrowableComputable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.resetAllRubyTypeProviderAndIDEACaches
import org.jetbrains.ruby.codeInsight.types.signature.CallInfo
import org.jetbrains.ruby.codeInsight.types.storage.server.DatabaseProvider
import org.jetbrains.ruby.codeInsight.types.storage.server.impl.CallInfoRow
import org.jetbrains.ruby.codeInsight.types.storage.server.impl.CallInfoTable
import java.io.File
const val CHUNK_SIZE = 1500
fun Database.copyTo(destination: Database, moveProgressBar: Boolean) {
var progressIndicator: ProgressIndicator? = null
var count: Int? = null
if (moveProgressBar) {
progressIndicator = ProgressManager.getInstance().progressIndicator
count = transaction(this) { CallInfoTable.selectAll().count() }
}
var offset = 0
while (true) {
val info: List<CallInfo> = transaction(this) {
CallInfoRow.wrapRows(CallInfoTable.selectAll().limit(CHUNK_SIZE, offset)).map { it.copy() }
}
if (info.isEmpty()) {
break
}
transaction(destination) {
info.forEach { CallInfoTable.insertInfoIfNotContains(it) }
}
offset += CHUNK_SIZE
if (moveProgressBar) {
progressIndicator!!.fraction = offset.toDouble() / count!!
}
}
}
class ExportContractsAction : ExportFileActionBase(
whatToExport = "Type contracts",
generateFilename = { project: Project -> "${project.name}-type-contracts" },
extensions = arrayOf("mv.db")
) {
override fun backgroundProcess(absoluteFilePath: String, module: Module?, sdk: Sdk?, project: Project) {
exportContractsToFile(absoluteFilePath, moveProgressBar = true)
}
companion object {
fun exportContractsToFile(pathToExport: String, moveProgressBar: Boolean) {
check(pathToExport.endsWith(DatabaseProvider.H2_DB_FILE_EXTENSION)) {
"Path to export must end with .mv.db"
}
File(pathToExport).delete()
val databaseToExportTo = DatabaseProvider.connectToDB(pathToExport)
DatabaseProvider.defaultDatabase!!.copyTo(databaseToExportTo, moveProgressBar)
}
}
}
class ImportContractsAction : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project
val files = FileChooserDialogImpl(
FileChooserDescriptor(true, false, false, false, false, false),
project).choose(project)
if (files.isEmpty()) {
return
}
try {
ProgressManager.getInstance().runProcessWithProgressSynchronously(ThrowableComputable<Unit, Exception> {
files.forEach { importContractsFromFile(it.path, moveProgressBar = true) }
return@ThrowableComputable
}, "Importing type contracts", false, project)
resetAllRubyTypeProviderAndIDEACaches(project)
} catch (ex: Exception) {
Messages.showErrorDialog(ex.message, "Error while importing type contracts")
}
}
companion object {
fun importContractsFromFile(pathToImportFrom: String, moveProgressBar: Boolean) {
check(pathToImportFrom.endsWith(DatabaseProvider.H2_DB_FILE_EXTENSION)) {
"Path to import from must end with .mv.db"
}
val dbToImportFrom = DatabaseProvider.connectToDB(pathToImportFrom)
dbToImportFrom.copyTo(DatabaseProvider.defaultDatabase!!, moveProgressBar)
}
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/ProjectLifecycleListenerImpl.kt
================================================
package org.jetbrains.plugins.ruby.ruby.codeInsight
import com.google.gson.Gson
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener
import org.jetbrains.plugins.ruby.ruby.persistent.TypeInferenceDirectory
import org.jetbrains.plugins.ruby.util.runServerAsyncInIDEACompatibleMode
import org.jetbrains.ruby.codeInsight.types.storage.server.DatabaseProvider
import org.jetbrains.ruby.runtime.signature.server.SignatureServer
import java.io.File
import java.io.PrintWriter
import java.nio.file.Paths
/**
* Short [Project] description for `rubymine-type-tracer`
*/
data class ProjectDescription(val projectName: String, val projectPath: String, val pipeFilePath: String) {
/**
* @param project default projects are not allowed!
*/
constructor(project: Project, pipeFilePath: String) : this(project.name, project.basePath!!, pipeFilePath)
}
/**
* This directory is needed for `rubymine-type-tracker` script
*
* In this directory we keep files named the same as currently opened projects in RubyMine.
* Each file contains projectPath of pipe file required for arg-scanner.
*/
private val openedProjectsDir = File(System.getProperty("java.io.tmpdir")!!).resolve(".ruby-type-inference")
.also { it.mkdirs() }
/**
* This registered in `plugin.xml` and it's constructor called every time RubyMine starts
*/
class ProjectLifecycleListenerImpl : ProjectManagerListener {
private val gson = Gson()
private companion object {
@Volatile
private var initialized: Boolean = false
}
override fun projectOpened(project: Project) {
if (!project.isDefault) {
connectToDB(project.name)
// This server is used for `rubymine-type-tracker` script
startNewBackgroundInfinityServer(project)
}
}
override fun projectClosed(project: Project) {
if (!project.isDefault) {
val projectDescription = readProjectDescription(project, deleteJsonAfterRead = true)
File(projectDescription.pipeFilePath).delete()
}
}
private fun connectToDB(projectName: String) {
val filePath = Paths.get(
TypeInferenceDirectory.RUBY_TYPE_INFERENCE_DIRECTORY.toString(),
projectName).toString() + DatabaseProvider.H2_DB_FILE_EXTENSION
DatabaseProvider.connectToDB(filePath, isDefaultDatabase = true)
}
/**
* Starts server for `rubymine-type-tracker` script
*/
private fun startNewBackgroundInfinityServer(project: Project): Boolean {
if (project.isDefault) {
return false
}
val server = SignatureServer()
val pipeFilePath: String = server.runServerAsyncInIDEACompatibleMode(project)
writeProjectDescription(ProjectDescription(project, pipeFilePath))
server.afterExitListener = {
startNewBackgroundInfinityServer(project)
}
return true
}
private fun writeProjectDescription(description: ProjectDescription) {
val jsonFile: File = openedProjectsDir.resolve(description.projectName)
PrintWriter(jsonFile).use { it.println(gson.toJson(description)) }
}
private fun readProjectDescription(project: Project, deleteJsonAfterRead: Boolean = false): ProjectDescription {
val jsonFile: File = openedProjectsDir.resolve(project.name)
val json: String = jsonFile.bufferedReader().use { it.readText() }
val description = gson.fromJson<ProjectDescription>(json, ProjectDescription::class.java)!!
if (deleteJsonAfterRead) {
jsonFile.delete()
}
return description
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/RubyDynamicCodeInsightPluginAppLifecyctlListener.kt
================================================
package org.jetbrains.plugins.ruby.ruby.codeInsight
import com.intellij.ide.AppLifecycleListener
import com.intellij.openapi.project.Project
import org.jetbrains.plugins.ruby.RubyDynamicCodeInsightPluginInjector
import org.jetbrains.ruby.codeInsight.initInjector
class RubyDynamicCodeInsightPluginAppLifecyctlListener : AppLifecycleListener {
override fun appStarting(projectFromCommandLine: Project?) {
initInjector(RubyDynamicCodeInsightPluginInjector)
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/TrackerDataLoader.kt
================================================
package org.jetbrains.plugins.ruby.ruby.codeInsight
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import org.jetbrains.plugins.ruby.ruby.codeInsight.stateTracker.RubyClassHierarchyWithCaching
class TrackerDataLoader : StartupActivity, DumbAware {
override fun runActivity(project: Project) {
ModuleManager.getInstance(project).modules.forEach {
RubyClassHierarchyWithCaching.loadFromSystemDirectory(it)
}
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/stateTracker/ClassHierarchySymbolProvider.kt
================================================
package org.jetbrains.plugins.ruby.ruby.codeInsight.stateTracker
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiElement
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.RubySymbolProviderBase
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.fqn.FQN
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.Symbol
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.v2.SymbolPsiProcessor
import org.jetbrains.plugins.ruby.ruby.lang.psi.RPsiElement
class ClassHierarchySymbolProvider : RubySymbolProviderBase() {
override fun processDynamicSymbols(symbol: Symbol, element: RPsiElement?, fqn: FQN, processor: SymbolPsiProcessor,
invocationPoint: PsiElement?): Boolean {
if (element == null) {
return true
}
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return true
val hierarchy = RubyClassHierarchyWithCaching.getInstance(module)?: return true
hierarchy.getMembersWithCaching(fqn.fullPath, symbol.rootSymbol).forEach {
if (!processor.process(it)) {
return false
}
}
return true
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/stateTracker/RubyClassHierarchyWithCaching.kt
================================================
package org.jetbrains.plugins.ruby.ruby.codeInsight.stateTracker
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.util.Key
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.Type
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.Types
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.RMethodSyntheticSymbol
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.Symbol
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.SymbolUtil
import org.jetbrains.plugins.ruby.ruby.persistent.TypeInferenceDirectory
import org.jetbrains.plugins.ruby.settings.RubyTypeContractsSettings
import org.jetbrains.ruby.stateTracker.*
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
class RubyClassHierarchyWithCaching private constructor(private val rubyClassHierachy: RubyClassHierarchy) {
private val lookupCache = ContainerUtil.createConcurrentWeakMap<Pair<String, String>, RubyMethod>()
private val membersCache = ContainerUtil.createConcurrentWeakMap<String, Set<Symbol>>()
fun getTypeForConstant(constant: String): RubyConstant? {
return rubyClassHierachy.topLevelConstants[constant]
}
fun getMembersWithCaching(moduleName: String, topLevel: Symbol) : Set<Symbol> {
val module = rubyClassHierachy.getRubyModule(moduleName) ?: return emptySet()
return getMembersWithCaching(module, topLevel)
}
private fun lookupInstanceMethodWithCaching(module: RubyModule, methodName: String) : RubyMethod? {
return lookupCache.computeIfAbsent(Pair(module.name, methodName)) { lookupInstanceMethod(module, methodName) }
}
private fun lookupInstanceMethod(module: RubyModule, methodName: String): RubyMethod? {
val ownResult = module.instanceMethods.firstOrNull {it.name == methodName}
if (ownResult != null) {
return ownResult
}
module.instanceDirectAncestors.forEach {
val result = lookupInstanceMethodWithCaching(it, methodName)
if (result != null) {
return result
}
}
if (module is RubyClass && module.superClass != RubyClass.EMPTY) {
val result = lookupInstanceMethodWithCaching(module.superClass, methodName)
if (result != null) {
return result
}
}
return null
}
private fun getMembersWithCaching(module: RubyModule, topLevel: Symbol) : Set<Symbol> {
return membersCache.computeIfAbsent(module.name) { getMembers(module, topLevel) }
}
private fun getMembers(module: RubyModule, topLevel: Symbol) : Set<Symbol> {
val set = HashSet<Symbol>()
val symbol = SymbolUtil.findSymbolInHierarchy(topLevel, module.name, Types.MODULE_OR_CLASS, topLevel.psiElement)
set.addAll(module.instanceMethods.map { RMethodSyntheticSymbol(topLevel.project, Type.INSTANCE_METHOD, it, symbol) })
set.addAll(module.classMethods.map { RMethodSyntheticSymbol(topLevel.project, Type.CLASS_METHOD, it, symbol) })
module.instanceDirectAncestors.forEach { set.addAll(getMembersWithCaching(it, topLevel))}
module.classDirectAncestors.forEach{ set.addAll(getMembersWithCaching(it, topLevel)) }
if (module is RubyClass && module.superClass != RubyClass.EMPTY) {
set.addAll(getMembersWithCaching(module.superClass, topLevel))
}
return set
}
companion object {
private val CLASS_HIERARCHY_KEY = Key<RubyClassHierarchyWithCaching>("org.jetbrains.plugins.ruby.ruby.codeInsight.stateTracker.ClassHierarchy")
private val CLASS_HIERARCHY_FILENAME = "-class-hierarchy.json.gz"
fun loadFromSystemDirectory(module: Module): RubyClassHierarchyWithCaching? {
val file = File(TypeInferenceDirectory.RUBY_TYPE_INFERENCE_DIRECTORY.toFile(),
module.project.name + "-" + module.name + CLASS_HIERARCHY_FILENAME)
if (!file.exists()) {
return null
}
FileInputStream(file).use {
GZIPInputStream(it).use {
val json = it.reader(Charsets.UTF_8).use{ it.readText() }
return createClassHierarchyFromJson(json, module)
}
}
}
@Synchronized
fun updateAndSaveToSystemDirectory(jsons: List<String>, module: Module) {
val json = RubyClassHierarchyLoader.mergeJsons(jsons)
createClassHierarchyFromJson(json, module)
FileOutputStream(File(TypeInferenceDirectory.RUBY_TYPE_INFERENCE_DIRECTORY.toFile(),
module.project.name + "-" + module.name + CLASS_HIERARCHY_FILENAME)).use {
GZIPOutputStream(it).use {
it.writer(Charsets.UTF_8).use { it.write(json) }
}
}
}
private fun createClassHierarchyFromJson(json: String, module: Module) : RubyClassHierarchyWithCaching {
val rubyClassHierarchy = RubyClassHierarchyWithCaching(RubyClassHierarchyLoader.fromJson(json))
module.putUserData(RubyClassHierarchyWithCaching.CLASS_HIERARCHY_KEY,
rubyClassHierarchy)
return rubyClassHierarchy
}
fun getInstance(module: Module): RubyClassHierarchyWithCaching? {
if (!ServiceManager.getService(module.project, RubyTypeContractsSettings::class.java).stateTrackerEnabled) {
return null
}
val ret = module.getUserData(CLASS_HIERARCHY_KEY)
if (ret != null) {
return ret
}
return null
}
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/symbols/structure/RMethodSyntheticSymbol.java
================================================
package org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ruby.rdoc.yard.psi.RangeInDocumentFakePsiElement;
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.Type;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.RType;
import org.jetbrains.plugins.ruby.ruby.lang.psi.RPsiElement;
import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.methods.ArgumentInfo;
import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.methods.Visibility;
import org.jetbrains.plugins.ruby.ruby.lang.psi.impl.controlStructures.methods.RCommandArgumentListImpl;
import org.jetbrains.plugins.ruby.ruby.lang.psi.methodCall.RCall;
import org.jetbrains.ruby.stateTracker.Location;
import org.jetbrains.ruby.stateTracker.RubyMethod;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class RMethodSyntheticSymbol extends SymbolImpl implements RMethodSymbol {
@NotNull
private final Visibility myVisibility;
@NotNull
private final List<ArgumentInfo> myArgsInfo;
@Nullable
private final String myPath;
private final int myLineno;
public RMethodSyntheticSymbol(@NotNull final Project project,
@NotNull final Type type,
@NotNull final RubyMethod rubyMethod,
@Nullable final Symbol parent) {
super(project, rubyMethod.getName(), type, parent);
myVisibility = Visibility.PUBLIC;
myArgsInfo = toArgsInfo(rubyMethod.getArguments());
final Location location = rubyMethod.getLocation();
if (location != null) {
myPath = location.getPath();
myLineno = location.getLineNo();
} else {
myPath = "";
myLineno = 0;
}
}
private List<ArgumentInfo> toArgsInfo(List<RubyMethod.ArgInfo> arguments) {
return arguments.stream().map(RMethodSyntheticSymbol::toArgumentInfo).collect(Collectors.toList());
}
private static ArgumentInfo toArgumentInfo(final @NotNull RubyMethod.ArgInfo argInfo) {
ArgumentInfo.Type type;
switch (argInfo.getKind()) {
case REQ:
type = ArgumentInfo.Type.SIMPLE;
break;
case OPT:
type = ArgumentInfo.Type.PREDEFINED;
break;
case REST:
type = ArgumentInfo.Type.ARRAY;
break;
case KEY:
type = ArgumentInfo.Type.NAMED;
break;
case KEY_REST:
type = ArgumentInfo.Type.HASH;
break;
case KEY_REQ:
type = ArgumentInfo.Type.KEYREQ;
break;
case BLOCK:
type = ArgumentInfo.Type.BLOCK;
break;
default:
throw new IllegalArgumentException(argInfo.getKind().toString());
}
return new ArgumentInfo(argInfo.getName(), type);
}
@NotNull
@Override
public String getName() {
//noinspection ConstantConditions
return super.getName();
}
@Override
public @Nullable
List<ArgumentInfo> getArgumentInfos() {
return myArgsInfo;
}
@Override
@Nullable
public List<ArgumentInfo> getArgumentInfos(boolean includeDefaultArgs) {
return null;
}
@Nullable
@Override
public RType getCallType(@Nullable final RCall call) {
return null;
}
@NotNull
@Override
public String getArgsPresentation() {
if (myArgsInfo.isEmpty()) {
return "";
} else {
return "(" + RCommandArgumentListImpl.getPresentableName(myArgsInfo) + ")";
}
}
@Override
public boolean isSynthetic() {
return false;
}
@NotNull
@Override
public Visibility getVisibility() {
return myVisibility;
}
@Override
public PsiElement getPsiElement() {
if (myPath == null) {
return null;
}
final VirtualFile virtualFile = VirtualFileManager.getInstance().findFileByUrl(VfsUtilCore.pathToUrl(myPath));
if (virtualFile == null) {
return null;
}
final PsiFile file = PsiManager.getInstance(getProject()).findFile(virtualFile);
if (file == null) {
return null;
}
return CachedValuesManager.getCachedValue(file, () ->
CachedValueProvider.Result.create(calcElement(file), file));
}
@NotNull
@Override
public Collection<PsiElement> getAllDeclarations(PsiElement invocationPoint) {
final PsiElement psiElement = getPsiElement();
return psiElement == null ? Collections.emptyList() : Collections.singletonList(psiElement);
}
@Nullable
private PsiElement calcElement(@NotNull PsiFile file) {
final Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file);
if (document == null) {
return null;
}
return ReadAction.compute(() -> {
int offset = document.getLineStartOffset(myLineno);
int nextLineOffset = document.getLineEndOffset(myLineno);
int curOffset = offset;
PsiElement psiElement;
do {
psiElement = file.findElementAt(curOffset);
if (psiElement == null) {
return null;
}
curOffset = psiElement.getTextRange().getEndOffset();
} while (!(psiElement instanceof RPsiElement) && curOffset < nextLineOffset);
if (psiElement instanceof RPsiElement) {
return psiElement;
}
psiElement = file.findElementAt(offset);
if (psiElement == null) {
return null;
}
final int startElementOffset = psiElement.getTextRange().getStartOffset();
final int endElementOffset = psiElement.getTextRange().getEndOffset();
int start = offset - startElementOffset;
int end = Math.min(nextLineOffset - startElementOffset, endElementOffset - startElementOffset);
return new MyFakeElement(psiElement, new TextRange(start, end), getName());
});
}
private static class MyFakeElement extends RangeInDocumentFakePsiElement {
@NotNull
private final String myName;
MyFakeElement(@NotNull PsiElement parent, @NotNull TextRange rangeInParent, @NotNull String name) {
super(parent, rangeInParent);
myName = name;
}
@NotNull
@Override
public String getName() {
return myName;
}
@Override
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
throw new IncorrectOperationException("not supported");
}
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/types/RubyCollectStateRunner.kt
================================================
package org.jetbrains.plugins.ruby.ruby.codeInsight.types
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.RunProfile
import com.intellij.execution.configurations.RunProfileState
import com.intellij.execution.executors.CollectStateExecutor
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.ui.RunContentDescriptor
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.plugins.ruby.ruby.run.configuration.AbstractRubyRunConfiguration
import org.jetbrains.plugins.ruby.ruby.run.configuration.CollectExecSettings
import org.jetbrains.plugins.ruby.ruby.run.configuration.RubyAbstractCommandLineState
import org.jetbrains.plugins.ruby.ruby.run.configuration.RubyProgramRunner
import java.io.IOException
class RubyCollectStateRunner : RubyProgramRunner() {
override fun canRun(executorId: String, profile: RunProfile): Boolean {
return executorId == CollectStateExecutor.EXECUTOR_ID && profile is AbstractRubyRunConfiguration<*>
}
@Throws(ExecutionException::class)
override fun doExecute(state: RunProfileState,
environment: ExecutionEnvironment): RunContentDescriptor? {
if (state is RubyAbstractCommandLineState) {
val newConfig = state.config.clone()
val pathToState = tryGenerateTmpDirPath()
CollectExecSettings.putTo(newConfig,
CollectExecSettings.createSettings(true,
false,
true,
pathToState
))
val newState = newConfig.getState(environment.executor, environment)
if (newState != null) {
return super.doExecute(newState, environment)
}
}
return null
}
private fun tryGenerateTmpDirPath(): String? {
try {
val tmpDir = FileUtil.createTempDirectory("state-tracker", "")
return tmpDir.absolutePath
} catch (ignored: IOException) {
return null
}
}
override fun getRunnerId(): String {
return RUBY_COLLECT_STATE_RUNNER_ID
}
companion object {
private val RUBY_COLLECT_STATE_RUNNER_ID = "RubyCollectState"
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/types/RubyRunWithTypeTrackerRunner.kt
================================================
package org.jetbrains.plugins.ruby.ruby.codeInsight.types
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.RunProfile
import com.intellij.execution.configurations.RunProfileState
import com.intellij.execution.executors.RunWithTypeTrackerExecutor
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.ui.RunContentDescriptor
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.plugins.ruby.ruby.run.configuration.AbstractRubyRunConfiguration
import org.jetbrains.plugins.ruby.ruby.run.configuration.CollectExecSettings
import org.jetbrains.plugins.ruby.ruby.run.configuration.RubyAbstractCommandLineState
import org.jetbrains.plugins.ruby.ruby.run.configuration.RubyProgramRunner
import org.jetbrains.plugins.ruby.settings.RubyTypeContractsSettings
import java.io.IOException
class RubyRunWithTypeTrackerRunner : RubyProgramRunner() {
@Throws(ExecutionException::class)
override fun doExecute(state: RunProfileState,
environment: ExecutionEnvironment): RunContentDescriptor? {
if (state is RubyAbstractCommandLineState) {
val (_, _, typeTrackerEnabled) = ServiceManager.getService(environment.project, RubyTypeContractsSettings::class.java)
val newConfig = state.config.clone()
val pathToState = tryGenerateTmpDirPath()
CollectExecSettings.putTo(newConfig,
CollectExecSettings.createSettings(true,
typeTrackerEnabled,
false,
pathToState
))
val newState = newConfig.getState(environment.executor, environment)
if (newState != null) {
return super.doExecute(newState, environment)
}
}
return null
}
override fun preloaderAllowed(): Boolean = false
private fun tryGenerateTmpDirPath(): String? = try {
val tmpDir = FileUtil.createTempDirectory("type-tracker", "")
tmpDir.absolutePath
} catch (ignored: IOException) {
null
}
override fun canRun(executorId: String, profile: RunProfile): Boolean {
return executorId == RunWithTypeTrackerExecutor.EXECUTOR_ID && profile is AbstractRubyRunConfiguration<*>
}
override fun getRunnerId(): String {
return RUBY_COLLECT_TYPE_RUNNER_ID
}
companion object {
private val RUBY_COLLECT_TYPE_RUNNER_ID = "RubyCollectType"
}
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/types/RubyTypeProvider.kt
================================================
package org.jetbrains.plugins.ruby.ruby.codeInsight.types
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.plugins.ruby.ruby.codeInsight.AbstractRubyTypeProvider
import org.jetbrains.plugins.ruby.ruby.codeInsight.IncomingType
import org.jetbrains.plugins.ruby.ruby.codeInsight.resolve.ResolveUtil
import org.jetbrains.plugins.ruby.ruby.codeInsight.stateTracker.RubyClassHierarchyWithCaching
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbolicExecution.SymbolicExecutionContext
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbolicExecution.SymbolicExpressionProvider
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbolicExecution.SymbolicTypeInferenceProvider
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbolicExecution.TypeInferenceComponent
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbolicExecution.instance.TypeInferenceInstance
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbolicExecution.symbolicExpression.SymbolicCall
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbolicExecution.symbolicExpression.SymbolicExpression
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.Type
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.Symbol
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.util.SymbolHierarchy
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.impl.REmptyType
import org.jetbrains.plugins.ruby.ruby.lang.psi.RPsiElement
import org.jetbrains.plugins.ruby.ruby.lang.psi.RubyPsiUtil
import org.jetbrains.plugins.ruby.ruby.lang.psi.expressions.RExpression
import org.jetbrains.plugins.ruby.ruby.lang.psi.variables.RIdentifier
import org.jetbrains.ruby.codeInsight.types.signature.*
import org.jetbrains.ruby.codeInsight.types.storage.server.impl.CallInfoTable
import org.jetbrains.ruby.codeInsight.types.storage.server.impl.RSignatureProviderImpl
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
/**
* Cache where we store last accessed [CallInfo]s. Thread safe
*/
private val registeredCallInfosCache: MutableMap<MethodInfo, List<CallInfo>>
= ContainerUtil.createConcurrentSoftKeySoftValueMap<MethodInfo, List<CallInfo>>()
fun resetAllRubyTypeProviderAndIDEACaches(project: Project?) {
registeredCallInfosCache.clear()
// Clears IDEAs caches about inferred types
ServiceManager.getService(project ?: return, TypeInferenceContext::class.java)?.clear()
}
fun getCachedOrComputedRegisteredCallInfo(methodInfo: MethodInfo): List<CallInfo> {
return registeredCallInfosCache.getOrPut(methodInfo) {
RSignatureProviderImpl.getRegisteredCallInfos(methodInfo)
}
}
class RubyParameterTypeProvider : AbstractRubyTypeProvider() {
override fun createTypeBySymbol(symbol: Symbol): RType? {
return null
}
override fun createTypeByRExpression(expr: RExpression): RType? {
val symbol = ResolveUtil.resolveToSymbolWithCaching(expr.reference, false)
if (symbol?.type == Type.CONSTANT) {
val module = ModuleUtilCore.findModuleForPsiElement(expr) ?: return null
val classHierarchyWithCaching = RubyClassHierarchyWithCaching.getInstance(module) ?: return null
val path = symbol?.fqnWithNesting?.fullPath ?: return null
val constant = classHierarchyWithCaching.getTypeForConstant(path) ?: return null
val originType = RTypeFactory.createTypeByFQN(expr.project, constant.type)
val mixins = constant.extended.map { RTypeFactory.createTypeByFQN(expr.project, it) }
if (mixins.isNotEmpty()) {
return RTypeUtil.union(originType, mixins.reduce { acc, it -> RTypeUtil.union(acc, it) })
}
return originType
}
if (expr is RIdentifier && expr.isParameter) {
val method = RubyPsiUtil.getContainingRMethod(expr) ?: return null
val rubyModuleName = RubyPsiUtil.getContainingRClassOrModule(method)?.fqn?.fullPath ?: "Object"
val info = MethodInfo.Impl(ClassInfo(rubyModuleName), method.fqn.shortName, RVisibility.PUBLIC)
val callInfos: List<CallInfo> = getCachedOrComputedRegisteredCallInfo(info)
val returnType = callInfos.map { callInfo ->
val typeName = expr.name?.let { callInfo.getTypeNameByArgumentName(it) } ?: return@map REmptyType.INSTANCE
return@map RTypeFactory.createTypeClassName(typeName, expr)
}.unionTypesSmart()
return if (returnType == REmptyType.INSTANCE) {
// If we don't have any information about type then return null
// in order to allow to RubyMine try to determine type itself
null
} else {
returnType
}
}
return null
}
}
/**
* Provides types for Ruby method return values. Type providing based on information collection into [CallInfoTable]
*/
class ReturnTypeSymbolicTypeInferenceProvider : SymbolicTypeInferenceProvider {
companion object {
private val pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1)
}
override fun evaluateSymbolicCall(symbolicCall: SymbolicCall,
context: SymbolicExecutionContext,
callContext: TypeInferenceInstance.CallContext,
provider: SymbolicExpressionProvider,
component: TypeInferenceComponent): SymbolicExpression? {
ProgressManager.checkCanceled()
val job: () -> SymbolicExpression? = {
evaluateSymbolicCallPoolJob(symbolicCall, context, callContext, component)
}
val future: Future<SymbolicExpression?> = pool.submit(job)
return try {
// This method is run under read action and we cannot afford to spend a lot of time determining time.
// Otherwise we got glitches see: https://youtrack.jetbrains.com/issue/RUBY-25433
future.get(100, TimeUnit.MILLISECONDS)
} catch (ex: TimeoutException) {
null
}
}
private fun evaluateSymbolicCallPoolJob(symbolicCall: SymbolicCall,
context: SymbolicExecutionContext,
callContext: TypeInferenceInstance.CallContext,
component: TypeInferenceComponent): SymbolicExpression? {
var unnamedArgsTypes: List<String?> = emptyList()
var namedArgsTypes: List<ArgumentNameAndType?> = emptyList()
var receiverTypesConsideringAncestors: List<String> = emptyList()
ReadAction.run<Exception> {
ProgressManager.checkCanceled()
val exactReceiverType: RType = SymbolicTypeInferenceProvider.getReceiverType(symbolicCall, component, callContext)
// reversed because getAncestorsCaching gives us list of ancestors ordered from parent to end children
// This list already include exactReceiverType
receiverTypesConsideringAncestors = RTypeUtil.getBirthTypeSymbol(exactReceiverType)
?.let { SymbolHierarchy.getAncestorsCaching(it, callContext.invocationPoint) }
?.map { it.symbol.fqnWithNesting.toString() }?.reversed() ?: emptyList()
val typeInferenceComponent = context.getComponent(TypeInferenceComponent::class.java)
unnamedArgsTypes = symbolicCall.arguments.asSequence().filter { it.type != IncomingType.ASSOC }
.map { typeInferenceComponent.getTypeForSymbolicExpression(it.expression).name }.toList()
namedArgsTypes = symbolicCall.arguments.asSequence().filter { it.type == IncomingType.ASSOC }
.map {
val type = typeInferenceComponent.getTypeForSymbolicExpression(it.expression).name ?: return@map null
return@map ArgumentNameAndType(it.keyName, type)
}.toList()
}
for (receiverTypeName in receiverTypesConsideringAncestors) {
val methodInfo = MethodInfo.Impl(ClassInfo.Impl(null, receiverTypeName), symbolicCall.name)
val registeredCallInfos = getCachedOrComputedRegisteredCallInfo(methodInfo)
val registeredReturnTypes: List<String> = registeredCallInfos
.asSequence()
.filter { argumentsMatch(it, unnamedArgsTypes, namedArgsTypes) }
.map { it.returnType }
.toList()
.takeIf { !it.isEmpty() }
?: registeredCallInfos.map { it.returnType }
val returnType = registeredReturnTypes
.mapNotNull {
RTypeFactory.createTypeClassName(it, callContext.invocationPoint as? RPsiElement
?: return@mapNotNull null)
}
.unionTypesSmart()
if (returnType != REmptyType.INSTANCE) {
component.updateSymbolicExpressionType(symbolicCall, returnType)
return symbolicCall
}
}
// If we don't have any information about type then return null
// in order to allow to RubyMine try to determine type itself
return null
}
private fun argumentsMatch(oneOfExpected: CallInfo, actualUnnamedArgs: List<String?>, actualNamedArgs: List<ArgumentNameAndType?>): Boolean {
if (oneOfExpected.unnamedArguments.size != actualUnnamedArgs.size || oneOfExpected.namedArguments.size != actualNamedArgs.size) {
return false
}
if (oneOfExpected.unnamedArguments.zip(actualUnnamedArgs).any {
it.first.type != ArgumentNameAndType.IMPLICITLY_PASSED_ARGUMENT_TYPE &&
it.second != null &&
it.first.type != it.second
}) {
return false
}
if (oneOfExpected.namedArguments.zip(actualNamedArgs).any {
it.second != null &&
it.first.type != ArgumentNameAndType.IMPLICITLY_PASSED_ARGUMENT_TYPE &&
it.second!!.type != ArgumentNameAndType.IMPLICITLY_PASSED_ARGUMENT_TYPE &&
it.first.type != it.second!!.type
}) {
return false
}
return true
}
}
/**
* The same as [unionTypes] but also get rid of [REmptyType] and duplicates
*/
private fun List<RType>.unionTypesSmart(): RType = filter { it != REmptyType.INSTANCE }.distinct().unionTypes()
private fun List<RType>.unionTypes(): RType {
if (size == 0) {
return REmptyType.INSTANCE
}
if (size == 1) {
return first()
}
val m = size / 2
return RTypeUtil.union(subList(0, m).unionTypes(), subList(m, size).unionTypes())
}
================================================
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/intentions/AddContractAnnotationIntention.java
================================================
package org.jetbrains.plugins.ruby.ruby.intentions;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.exposed.dao.EntityID;
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.fqn.FQN;
import org.jetbrains.plugins.ruby.ruby.lang.psi.RFile;
import org.jetbrains.plugins.ruby.ruby.lang.psi.RubyElementFactory;
import org.jetbrains.plugins.ruby.ruby.lang.psi.RubyPsiUtil;
import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.methods.RMethod;
import org.jetbrains.plugins.ruby.ruby.lang.psi.variables.RFName;
import org.jetbrains.ruby.codeInsight.types.signature.*;
import org.jetbrains.ruby.codeInsight.types.signature.contractTransition.ContractTransition;
import org.jetbrains.ruby.codeInsight.types.signature.contractTransition.ReferenceContractTransition;
import org.jetbrains.ruby.codeInsight.types.signature.contractTransition.TypedContractTransition;
import org.jetbrains.ruby.codeInsight.types.storage.server.DatabaseProvider;
import org.jetbrains.ruby.codeInsight.types.storage.server.impl.MethodInfoTable;
import org.jetbrains.ruby.codeInsight.types.storage.server.impl.RSignatureProviderImpl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AddContractAnnotationIntention extends BaseRubyMethodIntentionAction {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return getText();
}
public boolean isAvailable(@NotNull final Project project, final @NotNull Editor editor, @NotNull final PsiFile file) {
if (!super.isAvailable(project, editor, file)) {
return false;
}
if (!file.isWritable()) {
return false;
}
RFName rfName = getRFName(editor, file);
if (rfName == null) {
return false;
}
RMethod method = RubyPsiUtil.getContainingRMethod(rfName);
if (method == null) {
return false;
}
MethodInfo methodInfo = createMethodInfo(method);
EntityID<Integer> found = methodInfo != null ? DatabaseProvider.defaultDatabaseTransaction(
transaction -> MethodInfoTable.INSTANCE.findRowId(methodInfo)) : null;
return found != null;
}
pu
gitextract_m6imoy85/
├── .gitignore
├── .travis.yml
├── FEATURES.md
├── LICENSE
├── README.md
├── arg_scanner/
│ ├── .gitignore
│ ├── Gemfile
│ ├── LICENSE.txt
│ ├── README.md
│ ├── Rakefile
│ ├── arg_scanner.gemspec
│ ├── bin/
│ │ ├── arg-scanner
│ │ ├── console
│ │ ├── rubymine-type-tracker
│ │ └── setup
│ ├── ext/
│ │ └── arg_scanner/
│ │ ├── arg_scanner.c
│ │ ├── arg_scanner.h
│ │ └── extconf.rb
│ ├── lib/
│ │ ├── arg_scanner/
│ │ │ ├── options.rb
│ │ │ ├── require_all.rb
│ │ │ ├── starter.rb
│ │ │ ├── state_tracker.rb
│ │ │ ├── type_tracker.rb
│ │ │ ├── version.rb
│ │ │ └── workspace.rb
│ │ └── arg_scanner.rb
│ ├── test/
│ │ ├── helper.rb
│ │ ├── test_args_info.rb
│ │ ├── test_call_info.rb
│ │ └── test_state_tracker.rb
│ └── util/
│ └── state_filter.rb
├── build.gradle
├── common/
│ ├── build.gradle
│ └── src/
│ └── main/
│ └── java/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── codeInsight/
│ ├── Injector.kt
│ ├── Logger.kt
│ └── PrintToStdoutLogger.kt
├── contract-creator/
│ ├── build.gradle
│ └── src/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── runtime/
│ └── signature/
│ └── server/
│ ├── SignatureServer.kt
│ ├── SignatureServerInjector.kt
│ └── serialisation/
│ └── ServerResponseBean.kt
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── ide-plugin/
│ ├── CHANGELOG.md
│ ├── build.gradle
│ ├── resources/
│ │ └── META-INF/
│ │ └── plugin.xml
│ └── src/
│ ├── com/
│ │ └── intellij/
│ │ └── execution/
│ │ └── executors/
│ │ ├── CollectStateExecutor.kt
│ │ └── RunWithTypeTrackerExecutor.java
│ ├── org/
│ │ └── jetbrains/
│ │ └── plugins/
│ │ └── ruby/
│ │ ├── IdePluginLogger.kt
│ │ ├── PluginResourceUtil.java
│ │ ├── RubyDynamicCodeInsightPluginInjector.kt
│ │ ├── ancestorsextractor/
│ │ │ ├── AncestorsExtractor.kt
│ │ │ └── RailsConsoleRunner.kt
│ │ ├── ruby/
│ │ │ ├── actions/
│ │ │ │ ├── ExportAncestorsActions.kt
│ │ │ │ ├── ExportAncesttorsDiffAction.kt
│ │ │ │ ├── ExportFileActionBase.kt
│ │ │ │ └── ImportExportContractsAction.kt
│ │ │ ├── codeInsight/
│ │ │ │ ├── ProjectLifecycleListenerImpl.kt
│ │ │ │ ├── RubyDynamicCodeInsightPluginAppLifecyctlListener.kt
│ │ │ │ ├── TrackerDataLoader.kt
│ │ │ │ ├── stateTracker/
│ │ │ │ │ ├── ClassHierarchySymbolProvider.kt
│ │ │ │ │ └── RubyClassHierarchyWithCaching.kt
│ │ │ │ ├── symbols/
│ │ │ │ │ └── structure/
│ │ │ │ │ └── RMethodSyntheticSymbol.java
│ │ │ │ └── types/
│ │ │ │ ├── RubyCollectStateRunner.kt
│ │ │ │ ├── RubyRunWithTypeTrackerRunner.kt
│ │ │ │ └── RubyTypeProvider.kt
│ │ │ ├── intentions/
│ │ │ │ ├── AddContractAnnotationIntention.java
│ │ │ │ ├── BaseRubyMethodIntentionAction.kt
│ │ │ │ └── RemoveCollectedInfoIntention.kt
│ │ │ ├── persistent/
│ │ │ │ └── TypeInferenceDirectory.kt
│ │ │ └── run/
│ │ │ └── configuration/
│ │ │ ├── CollectExecSettings.java
│ │ │ └── RunWithTypeTrackerRunConfigurationExtension.java
│ │ ├── settings/
│ │ │ ├── RubyTypeContractsConfigurable.kt
│ │ │ ├── RubyTypeContractsConfigurableUI.kt
│ │ │ └── RubyTypeContractsSettings.kt
│ │ └── util/
│ │ └── SignatureServerUtil.kt
│ └── test/
│ ├── java/
│ │ ├── CallStatCompletionTest.kt
│ │ └── org/
│ │ └── jetbrains/
│ │ └── plugins/
│ │ └── ruby/
│ │ └── ruby/
│ │ └── actions/
│ │ └── ImportExportTests.kt
│ └── testData/
│ ├── anonymous_module_method_call_test.rb
│ ├── call_info_of_nested_class_test.rb
│ ├── duplicates_in_callinfo_table_test.rb
│ ├── forget_call_info_when_arguments_number_changed_test_part_1.rb
│ ├── forget_call_info_when_arguments_number_changed_test_part_2.rb
│ ├── in_project_root_test/
│ │ ├── gem_like.rb
│ │ └── in_project_root_test.rb
│ ├── merge_test1.rb
│ ├── merge_test1_to_run.rb
│ ├── merge_test2.rb
│ ├── merge_test2_to_run.rb
│ ├── method_without_parameters_test.rb
│ ├── multiple_execution_test1.rb
│ ├── multiple_execution_test2.rb
│ ├── multiple_execution_test2_to_run.rb
│ ├── ref_links_test.rb
│ ├── ref_links_test_to_run.rb
│ ├── ruby_exec_part_2.rb
│ ├── ruby_exec_test.rb
│ ├── sample_kw_test.rb
│ ├── sample_kw_test_to_run.rb
│ ├── sample_test.rb
│ ├── sample_test_to_run.rb
│ ├── save_types_between_launches_test_part_1.rb
│ ├── save_types_between_launches_test_part_2.rb
│ ├── simple_call_info_collection_test.rb
│ ├── simple_call_info_collection_test_multiple_functions_test.rb
│ ├── simple_call_info_collection_with_multiple_arguments_test.rb
│ └── top_level_methods_call_info_collection_test.rb
├── ruby-call-signature/
│ ├── build.gradle
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── org/
│ │ └── jetbrains/
│ │ └── ruby/
│ │ └── codeInsight/
│ │ └── types/
│ │ └── signature/
│ │ ├── CallInfo.kt
│ │ ├── ClassInfo.kt
│ │ ├── GemInfo.kt
│ │ ├── MethodInfo.kt
│ │ ├── ParameterInfo.java
│ │ ├── RSignatureContract.java
│ │ ├── RSignatureContractContainer.kt
│ │ ├── RSignatureContractNode.java
│ │ ├── RTuple.java
│ │ ├── SignatureContract.kt
│ │ ├── SignatureInfo.kt
│ │ ├── contractTransition/
│ │ │ ├── ContractTransition.java
│ │ │ ├── ReferenceContractTransition.java
│ │ │ ├── TransitionHelper.java
│ │ │ └── TypedContractTransition.java
│ │ └── serialization/
│ │ ├── MethodInfoSerialization.kt
│ │ ├── RmcDirectory.kt
│ │ ├── SignatureContractSerialization.kt
│ │ └── TestSerialization.kt
│ └── test/
│ └── java/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── codeInsight/
│ └── types/
│ └── signature/
│ ├── GemInfoFromPathTest.kt
│ ├── SignatureContractMergeTest.kt
│ ├── SignatureContractSerializationTest.kt
│ └── SignatureContractTestBase.kt
├── settings.gradle
├── signature-viewer/
│ ├── build.gradle
│ └── src/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── runtime/
│ └── signature/
│ ├── DBViewer.kt
│ ├── EraseLocation.kt
│ ├── SignatureExport.kt
│ ├── SignatureImport.kt
│ ├── SignatureViewer.kt
│ └── SplitDB.kt
├── state-tracker/
│ ├── build.gradle
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── org/
│ │ └── jetbrains/
│ │ └── ruby/
│ │ └── stateTracker/
│ │ ├── RubyClassHierarchy.kt
│ │ └── RubyClassHierarchyLoader.kt
│ └── test/
│ └── java/
│ ├── org/
│ │ └── jetbrains/
│ │ └── ruby/
│ │ └── stateTracker/
│ │ ├── RubyClassHierarchyLoaderNonStandardModuleTypeTest.kt
│ │ └── RubyClassHierarchyLoaderTest.kt
│ └── testData/
│ ├── classes.json
│ └── non-standard-module-type.json
└── storage-server-api/
├── build.gradle
└── src/
├── main/
│ └── java/
│ └── org/
│ └── jetbrains/
│ └── ruby/
│ └── codeInsight/
│ └── types/
│ ├── signature/
│ │ └── serialization/
│ │ └── BlobSerialization.kt
│ └── storage/
│ └── server/
│ ├── DatabaseProvider.kt
│ ├── RSignatureProvider.java
│ ├── RSignatureStorage.java
│ ├── StorageException.java
│ ├── impl/
│ │ ├── IntIdTableWithPossibleDependency.kt
│ │ ├── RSignatureProviderImpl.kt
│ │ ├── RowConversions.kt
│ │ └── Schema.kt
│ └── testutil/
│ └── DatabaseTestUtils.kt
└── test/
└── java/
└── org/
└── jetbrains/
└── ruby/
└── codeInsight/
└── types/
└── storage/
└── server/
└── impl/
└── RSignatureProviderTest.kt
SYMBOL INDEX (380 symbols across 59 files)
FILE: arg_scanner/ext/arg_scanner/arg_scanner.c
type rb_trace_arg_t (line 30) | typedef struct rb_trace_arg_struct rb_trace_arg_t;
type call_info_t (line 48) | typedef struct
type signature_t (line 54) | typedef struct
function call_info_t_free (line 98) | static void call_info_t_free(call_info_t s)
function signature_t_free (line 103) | static void signature_t_free(signature_t *s)
function signature_t_free_partially (line 115) | static void signature_t_free_partially(signature_t *s)
function gint (line 125) | static gint
function gint (line 146) | static gint
function start_with (line 166) | inline int start_with(const char *str, const char *prefix) {
function file_exists (line 184) | static int file_exists(const char *file_path) {
function VALUE (line 188) | static VALUE init(VALUE self, VALUE pipe_file_path, VALUE buffering,
function Init_arg_scanner (line 221) | void Init_arg_scanner() {
function push_to_call_stack (line 244) | inline void push_to_call_stack(signature_t *signature) {
function signature_t (line 248) | inline signature_t *pop_from_call_stack() {
function is_call_stack_empty (line 260) | inline int is_call_stack_empty() {
function signature_t (line 267) | inline signature_t *top_of_call_stack() {
function rb_control_frame_t (line 274) | rb_control_frame_t *
function VALUE (line 286) | static VALUE exit_from_handle_call_skipping_call() {
function VALUE (line 291) | static VALUE
function VALUE (line 352) | static VALUE
function call_info_t (line 408) | static call_info_t
function contains (line 555) | static int contains(const char *const *container, const char *element) {
function join_kw_names_and_types (line 583) | static int join_kw_names_and_types(VALUE key, VALUE val, VALUE ignored) {
function VALUE (line 775) | static VALUE
function VALUE (line 791) | static VALUE
function is_call_info_needed (line 823) | static bool
function VALUE (line 839) | static VALUE
function VALUE (line 849) | static VALUE
FILE: arg_scanner/ext/arg_scanner/extconf.rb
class NilClass (line 8) | class NilClass
method empty? (line 9) | def empty?; true; end
function real_have_header (line 13) | def real_have_header(header_name)
FILE: arg_scanner/lib/arg_scanner.rb
type ArgScanner (line 6) | module ArgScanner
FILE: arg_scanner/lib/arg_scanner/options.rb
type ArgScanner (line 3) | module ArgScanner
function set_env (line 14) | def OPTIONS.set_env
FILE: arg_scanner/lib/arg_scanner/require_all.rb
type RequireAll (line 22) | module RequireAll
function require_all (line 49) | def require_all(*args)
function require_rel (line 161) | def require_rel(*paths)
function load_all (line 173) | def load_all(*paths)
function load_rel (line 179) | def load_rel(*paths)
function autoload_all (line 225) | def autoload_all(*paths)
function autoload_rel (line 239) | def autoload_rel(*paths)
function __autoload (line 257) | def __autoload(file, full_path, options)
FILE: arg_scanner/lib/arg_scanner/state_tracker.rb
type ArgScanner (line 6) | module ArgScanner
class StateTracker (line 7) | class StateTracker
method initialize (line 8) | def initialize
method require_extra_libs (line 22) | def require_extra_libs
method print_json (line 33) | def print_json(file)
method parse_top_level_constants (line 43) | def parse_top_level_constants
method get_extra_methods (line 56) | def get_extra_methods(value)
method method_to_json (line 60) | def method_to_json(method)
method module_to_json (line 74) | def module_to_json(mod)
method modules_to_json (line 90) | def modules_to_json
FILE: arg_scanner/lib/arg_scanner/type_tracker.rb
type ArgScanner (line 8) | module ArgScanner
class TypeTrackerPerformanceMonitor (line 10) | class TypeTrackerPerformanceMonitor
method initialize (line 11) | def initialize
method on_call (line 21) | def on_call
method on_return (line 25) | def on_return
method on_handled_return (line 36) | def on_handled_return
class TypeTracker (line 51) | class TypeTracker
method initialize (line 54) | def initialize
FILE: arg_scanner/lib/arg_scanner/version.rb
type ArgScanner (line 1) | module ArgScanner
FILE: arg_scanner/lib/arg_scanner/workspace.rb
type ArgScanner (line 1) | module ArgScanner
class Workspace (line 2) | class Workspace
method initialize (line 4) | def initialize
method on_process_start (line 9) | def on_process_start
method open_output_json (line 14) | def open_output_json(prefix)
method on_process_exit (line 22) | def on_process_exit
FILE: arg_scanner/test/helper.rb
class TestTypeTracker (line 5) | class TestTypeTracker
method initialize (line 11) | def initialize
method enable (line 25) | def enable(*args, &b)
method signatures (line 29) | def signatures
FILE: arg_scanner/test/test_args_info.rb
class TestArgsInfoWrapper (line 5) | class TestArgsInfoWrapper
method foo (line 7) | def foo(a); end
method foo2 (line 9) | def foo2(a, b = 1); end
method foo3 (line 11) | def foo3(**rest); end
method foo4 (line 13) | def foo4(kw: :symbol, **rest1); end
method foo5 (line 15) | def foo5(kw:, **rest); end
method foo6 (line 17) | def foo6(a, *rest, b); end
method initialize (line 19) | def initialize
class TestArgsInfo (line 33) | class TestArgsInfo < Test::Unit::TestCase
method setup (line 40) | def setup
method teardown (line 45) | def teardown
method test_simple_kwrest (line 49) | def test_simple_kwrest
method test_empty_kwrest (line 57) | def test_empty_kwrest
method test_req_and_opt_arg (line 65) | def test_req_and_opt_arg
method test_optkw_and_empty_kwrest (line 74) | def test_optkw_and_empty_kwrest
method test_reqkw_and_empty_kwrest (line 82) | def test_reqkw_and_empty_kwrest
method test_reqkw_and_kwrest (line 90) | def test_reqkw_and_kwrest
method test_optkw_and_kwrest (line 98) | def test_optkw_and_kwrest
method test_optkw_passed_and_kwrest (line 106) | def test_optkw_passed_and_kwrest
method test_rest (line 114) | def test_rest
method test_empty_rest (line 124) | def test_empty_rest
FILE: arg_scanner/test/test_call_info.rb
class TestCallInfoWrapper (line 4) | class TestCallInfoWrapper
method sqr (line 6) | def sqr(z1 = 10, z2 = 11, z3 = 13, z4 = 14, z5, z6, z7, z8, y: '0', x:...
method sqr2 (line 10) | def sqr2(z0, z1 = 2, z2 = 10, z3 = 2, z4 = 0, y: 1, x: 30, z: '40')
method foo (line 14) | def foo(a, b, c, *d, e)
method foo2 (line 18) | def foo2(*args)
method foo3 (line 22) | def foo3(b: 2, c: '3', **args)
method foo4 (line 26) | def foo4(b: 2, c:, d: "1", dd: 1, ddd: '111', **args)
method foo5 (line 30) | def foo5(b)
class TestCallInfo (line 36) | class TestCallInfo < Test::Unit::TestCase
method setup (line 42) | def setup
method teardown (line 47) | def teardown
method test_simple (line 51) | def test_simple
method test_simple_req_arg (line 62) | def test_simple_req_arg
method test_simple_kw (line 70) | def test_simple_kw
method test_rest (line 82) | def test_rest
method test_post_and_rest (line 93) | def test_post_and_rest
method test_kwrest (line 105) | def test_kwrest
method test_rest_and_reqkw_args (line 117) | def test_rest_and_reqkw_args
FILE: arg_scanner/test/test_state_tracker.rb
class StateTrackerTest (line 6) | class StateTrackerTest < Test::Unit::TestCase
method startup (line 10) | def startup
method test_has_struct (line 26) | def test_has_struct
method test_symbol_is_fine (line 30) | def test_symbol_is_fine
method test_loaded_path_is_fine (line 44) | def test_loaded_path_is_fine
method test_constant_is_fine (line 49) | def test_constant_is_fine
method get_class_method (line 59) | def get_class_method(symbol, name)
method get_instance_method (line 63) | def get_instance_method(symbol, name)
method get_class_with_name (line 67) | def get_class_with_name(name)
method get_named_entity (line 71) | def get_named_entity(obj, index, name)
FILE: ide-plugin/src/com/intellij/execution/executors/RunWithTypeTrackerExecutor.java
class RunWithTypeTrackerExecutor (line 15) | public class RunWithTypeTrackerExecutor extends Executor {
method RunWithTypeTrackerExecutor (line 22) | public RunWithTypeTrackerExecutor() {
method getToolWindowId (line 29) | @Override
method getToolWindowIcon (line 34) | @Override
method getIcon (line 39) | @NotNull
method getDisabledIcon (line 45) | @Nullable
method getDescription (line 51) | @NotNull
method getActionName (line 57) | @NotNull
method getId (line 63) | @NotNull
method getStartActionText (line 69) | @NotNull
method getContextActionId (line 74) | @NotNull
method getHelpId (line 80) | @Nullable
method getStartActionText (line 86) | @NotNull
method escapeMnemonicsInConfigurationName (line 94) | @NotNull
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/PluginResourceUtil.java
class PluginResourceUtil (line 10) | public final class PluginResourceUtil {
method PluginResourceUtil (line 13) | private PluginResourceUtil() {
method getPluginResourcesPath (line 16) | @NotNull
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/symbols/structure/RMethodSyntheticSymbol.java
class RMethodSyntheticSymbol (line 35) | public class RMethodSyntheticSymbol extends SymbolImpl implements RMetho...
method RMethodSyntheticSymbol (line 45) | public RMethodSyntheticSymbol(@NotNull final Project project,
method toArgsInfo (line 62) | private List<ArgumentInfo> toArgsInfo(List<RubyMethod.ArgInfo> argumen...
method toArgumentInfo (line 66) | private static ArgumentInfo toArgumentInfo(final @NotNull RubyMethod.A...
method getName (line 96) | @NotNull
method getArgumentInfos (line 103) | @Override
method getArgumentInfos (line 109) | @Override
method getCallType (line 115) | @Nullable
method getArgsPresentation (line 121) | @NotNull
method isSynthetic (line 131) | @Override
method getVisibility (line 136) | @NotNull
method getPsiElement (line 142) | @Override
method getAllDeclarations (line 161) | @NotNull
method calcElement (line 168) | @Nullable
class MyFakeElement (line 203) | private static class MyFakeElement extends RangeInDocumentFakePsiEleme...
method MyFakeElement (line 207) | MyFakeElement(@NotNull PsiElement parent, @NotNull TextRange rangeIn...
method getName (line 212) | @NotNull
method setName (line 218) | @Override
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/intentions/AddContractAnnotationIntention.java
class AddContractAnnotationIntention (line 36) | public class AddContractAnnotationIntention extends BaseRubyMethodIntent...
method getFamilyName (line 37) | @Nls(capitalization = Nls.Capitalization.Sentence)
method isAvailable (line 44) | public boolean isAvailable(@NotNull final Project project, final @NotN...
method invoke (line 67) | public void invoke(@NotNull final Project project, final Editor editor...
method createMethodInfo (line 90) | @Nullable
method dfs (line 102) | private void dfs(@NotNull SignatureNode v, @NotNull StringBuilder curr...
method addEdge (line 119) | private StringBuilder addEdge(@NotNull StringBuilder currentLine, Stri...
method edgeToStr (line 123) | private String edgeToStr(ContractTransition edge) {
method getTextByRubyFunctionNamePsiElement (line 131) | @NotNull
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/run/configuration/CollectExecSettings.java
class CollectExecSettings (line 7) | public class CollectExecSettings {
method isArgScannerEnabled (line 18) | public boolean isArgScannerEnabled() {
method isStateTrackerEnabled (line 22) | public boolean isStateTrackerEnabled() {
method setStateTrackerEnabled (line 26) | public void setStateTrackerEnabled(boolean myStateTrackerEnabled) {
method setArgScannerEnabled (line 30) | public void setArgScannerEnabled(boolean myArgScannerEnabled) {
method isTypeTrackerEnabled (line 34) | public boolean isTypeTrackerEnabled() {
method setTypeTrackerEnabled (line 38) | public void setTypeTrackerEnabled(boolean myTypeTrackerEnabled) {
method getOutputDirectory (line 42) | @Nullable
method setReturnTypeTrackerPath (line 47) | public void setReturnTypeTrackerPath(final @Nullable String path) {
method getFrom (line 51) | @NotNull
method putTo (line 57) | public static void putTo(@NotNull final AbstractRubyRunConfiguration c...
method createSettings (line 62) | public static CollectExecSettings createSettings(final boolean argScan...
FILE: ide-plugin/src/org/jetbrains/plugins/ruby/ruby/run/configuration/RunWithTypeTrackerRunConfigurationExtension.java
class RunWithTypeTrackerRunConfigurationExtension (line 37) | public class RunWithTypeTrackerRunConfigurationExtension extends RubyRun...
method readExternal (line 58) | @Override
method getEditorTitle (line 64) | @Nullable
method isApplicableFor (line 70) | @Override
method isEnabledFor (line 75) | @Override
method patchCommandLine (line 82) | @Override
method validateConfiguration (line 122) | @Override
method getRequireKeyForGem (line 157) | @Nullable
method attachToProcess (line 171) | @Override
method checkForPidFiles (line 187) | private boolean checkForPidFiles(final @NotNull File directory) {
method waitAllProcess (line 192) | private void waitAllProcess(final @NotNull File directory,
method processStateTrackerResult (line 204) | private void processStateTrackerResult(final @NotNull CollectExecSetti...
FILE: ide-plugin/src/test/testData/anonymous_module_method_call_test.rb
type A (line 1) | module A
function foo (line 2) | def self.foo(a, b)
FILE: ide-plugin/src/test/testData/call_info_of_nested_class_test.rb
type M (line 1) | module M
class A (line 2) | class A
method foo (line 3) | def foo(a)
FILE: ide-plugin/src/test/testData/duplicates_in_callinfo_table_test.rb
function foo (line 1) | def foo(a)
FILE: ide-plugin/src/test/testData/forget_call_info_when_arguments_number_changed_test_part_1.rb
class A (line 1) | class A
method foo (line 2) | def foo(a)
FILE: ide-plugin/src/test/testData/forget_call_info_when_arguments_number_changed_test_part_2.rb
class A (line 1) | class A
method foo (line 2) | def foo(a, b)
FILE: ide-plugin/src/test/testData/in_project_root_test/gem_like.rb
function catch (line 1) | def catch(a); end
function dont_catch_2 (line 3) | def dont_catch_2(a); end
function catch_2 (line 5) | def catch_2(a)
function dont_catch_3 (line 9) | def dont_catch_3(&a)
function catch_3 (line 13) | def catch_3(&a)
FILE: ide-plugin/src/test/testData/in_project_root_test/in_project_root_test.rb
function foo (line 7) | def foo(a); end
FILE: ide-plugin/src/test/testData/merge_test1.rb
class A (line 1) | class A
class C (line 5) | class C
class B1 (line 9) | class B1
method test1 (line 10) | def test1
method test2 (line 14) | def test2
class B2 (line 19) | class B2
method test3 (line 20) | def test3
method test4 (line 24) | def test4
FILE: ide-plugin/src/test/testData/merge_test1_to_run.rb
class A (line 1) | class A
class C (line 5) | class C
class B1 (line 9) | class B1
method test1 (line 10) | def test1
method test2 (line 14) | def test2
class B2 (line 19) | class B2
method test3 (line 20) | def test3
method test4 (line 24) | def test4
FILE: ide-plugin/src/test/testData/merge_test2.rb
class A (line 1) | class A
class C (line 5) | class C
class B1 (line 9) | class B1
method test1 (line 10) | def test1
method test2 (line 14) | def test2
class B2 (line 19) | class B2
method test3 (line 20) | def test3
method test4 (line 24) | def test4
FILE: ide-plugin/src/test/testData/merge_test2_to_run.rb
class A (line 1) | class A
class C (line 5) | class C
class B1 (line 9) | class B1
method test1 (line 10) | def test1
method test2 (line 14) | def test2
class B2 (line 19) | class B2
method test3 (line 20) | def test3
method test4 (line 24) | def test4
FILE: ide-plugin/src/test/testData/method_without_parameters_test.rb
function foo (line 1) | def foo
FILE: ide-plugin/src/test/testData/multiple_execution_test1.rb
class A (line 3) | class A
class C (line 7) | class C
class B (line 11) | class B
method test1 (line 12) | def test1
method test2 (line 16) | def test2
FILE: ide-plugin/src/test/testData/multiple_execution_test2.rb
class A (line 3) | class A
class C (line 7) | class C
class B (line 11) | class B
method test1 (line 12) | def test1
method test2 (line 16) | def test2
FILE: ide-plugin/src/test/testData/multiple_execution_test2_to_run.rb
class A (line 3) | class A
class C (line 7) | class C
class B (line 11) | class B
method test1 (line 12) | def test1
method test2 (line 16) | def test2
FILE: ide-plugin/src/test/testData/ref_links_test.rb
class A (line 1) | class A
class B (line 6) | class B
method test1 (line 7) | def test1
method test2 (line 11) | def test2
FILE: ide-plugin/src/test/testData/ref_links_test_to_run.rb
class A (line 1) | class A
class B (line 5) | class B
method test1 (line 6) | def test1
method test2 (line 10) | def test2
FILE: ide-plugin/src/test/testData/ruby_exec_part_2.rb
function bar (line 1) | def bar(a); end
FILE: ide-plugin/src/test/testData/ruby_exec_test.rb
function foo (line 1) | def foo(a); end
FILE: ide-plugin/src/test/testData/sample_kw_test.rb
class A (line 3) | class A
class C (line 7) | class C
class B (line 11) | class B
method test1 (line 12) | def test1
method test2 (line 16) | def test2
FILE: ide-plugin/src/test/testData/sample_kw_test_to_run.rb
class A (line 3) | class A
class C (line 7) | class C
class B (line 11) | class B
method test1 (line 12) | def test1
method test2 (line 16) | def test2
FILE: ide-plugin/src/test/testData/sample_test.rb
class A (line 3) | class A
class C (line 7) | class C
class B (line 11) | class B
method test1 (line 12) | def test1
method test2 (line 16) | def test2
FILE: ide-plugin/src/test/testData/sample_test_to_run.rb
class A (line 3) | class A
class C (line 7) | class C
class B (line 11) | class B
method test1 (line 12) | def test1
method test2 (line 16) | def test2
FILE: ide-plugin/src/test/testData/save_types_between_launches_test_part_1.rb
class A (line 1) | class A
method foo (line 2) | def foo(a)
FILE: ide-plugin/src/test/testData/save_types_between_launches_test_part_2.rb
class A (line 1) | class A
method foo (line 2) | def foo(a)
FILE: ide-plugin/src/test/testData/simple_call_info_collection_test.rb
class AClass (line 1) | class AClass
method foo (line 2) | def foo(a)
FILE: ide-plugin/src/test/testData/simple_call_info_collection_test_multiple_functions_test.rb
class A (line 1) | class A
method foo (line 2) | def foo(a, b)
method bar (line 6) | def bar(a)
FILE: ide-plugin/src/test/testData/simple_call_info_collection_with_multiple_arguments_test.rb
class AClass (line 1) | class AClass
method foo (line 2) | def foo(a, b)
FILE: ide-plugin/src/test/testData/top_level_methods_call_info_collection_test.rb
function foo (line 1) | def foo(a, b)
FILE: ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/ParameterInfo.java
class ParameterInfo (line 5) | public class ParameterInfo {
method ParameterInfo (line 11) | public ParameterInfo(@NotNull final String name, @NotNull final Type m...
method getName (line 16) | @NotNull
method getModifier (line 21) | @NotNull
method isNamedParameter (line 26) | public boolean isNamedParameter() {
method equals (line 30) | @Override
method hashCode (line 42) | @Override
type Type (line 59) | public enum Type {
FILE: ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/RSignatureContract.java
class RSignatureContract (line 13) | public class RSignatureContract implements SignatureContract {
method RSignatureContract (line 24) | public RSignatureContract(@NotNull RTuple tuple) {
method RSignatureContract (line 38) | public RSignatureContract(@NotNull List<ParameterInfo> argsInfo,
method RSignatureContract (line 50) | private RSignatureContract(@NotNull SignatureContract source) {
method getStartNode (line 93) | @NotNull
method getArgsInfo (line 99) | @NotNull
method getNodeCount (line 105) | public int getNodeCount() {
method copy (line 109) | @NotNull
method addRTuple (line 134) | public synchronized boolean addRTuple(@NotNull RTuple tuple) {
method minimize (line 169) | synchronized void minimize() {
method getLevels (line 221) | @TestOnly
method AddToBfsQueueAndUse (line 227) | private void AddToBfsQueueAndUse(@NotNull SignatureNode oldNode, @NotN...
method mergeWith (line 239) | public synchronized boolean mergeWith(@NotNull SignatureContract addit...
method createNodeAndAddToLevels (line 304) | @Nullable
method mergeMutably (line 321) | @Nullable
class Immutable (line 334) | private static class Immutable implements SignatureContract {
method Immutable (line 343) | private Immutable(@NotNull SignatureNode startNode, int nodeCount, @...
method getNodeCount (line 349) | @Override
method getStartNode (line 354) | @NotNull
method getArgsInfo (line 360) | @NotNull
class PairOfNodes (line 367) | private static class PairOfNodes {
method pairGoByTransition (line 373) | @NotNull
method PairOfNodes (line 378) | PairOfNodes(@NotNull SignatureNode node1, @NotNull SignatureNode nod...
FILE: ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/RSignatureContractNode.java
class RSignatureContractNode (line 9) | public class RSignatureContractNode implements SignatureNode {
method RSignatureContractNode (line 14) | public RSignatureContractNode() {
method addLink (line 18) | public void addLink(final @NotNull ContractTransition transition, @Not...
method getTransitions (line 22) | @NotNull
FILE: ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/RTuple.java
class RTuple (line 7) | public class RTuple {
method RTuple (line 19) | public RTuple(@NotNull final MethodInfo methodInfo,
method getMethodInfo (line 29) | @NotNull
method getArgsInfo (line 34) | @NotNull
method getArgsTypes (line 39) | @NotNull
method getReturnTypeName (line 44) | @NotNull
method equals (line 49) | @Override
method hashCode (line 62) | @Override
FILE: ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/contractTransition/ContractTransition.java
type ContractTransition (line 8) | public interface ContractTransition {
method getValue (line 16) | @NotNull
FILE: ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/contractTransition/ReferenceContractTransition.java
class ReferenceContractTransition (line 9) | public class ReferenceContractTransition implements ContractTransition {
method ReferenceContractTransition (line 13) | public ReferenceContractTransition(int mask) {
method getValue (line 17) | @NotNull
method getMask (line 41) | public int getMask() {
method equals (line 45) | @Override
method hashCode (line 55) | @Override
FILE: ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/contractTransition/TransitionHelper.java
class TransitionHelper (line 7) | public class TransitionHelper {
method TransitionHelper (line 8) | private TransitionHelper() {
method calculateTransition (line 11) | @NotNull
method getNewMask (line 21) | private static int getNewMask(@NotNull List<String> argsTypes, int arg...
FILE: ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/contractTransition/TypedContractTransition.java
class TypedContractTransition (line 9) | public class TypedContractTransition implements ContractTransition {
method TypedContractTransition (line 14) | public TypedContractTransition(@NotNull String type) {
method getValue (line 18) | @NotNull
method getType (line 24) | @NotNull
method equals (line 29) | @Override
method hashCode (line 39) | @Override
FILE: storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/RSignatureProvider.java
type RSignatureProvider (line 27) | public interface RSignatureProvider {
method getRegisteredGems (line 28) | @NotNull
method getClosestRegisteredGem (line 31) | @Nullable
method getRegisteredClasses (line 34) | @NotNull
method getAllClassesWithFQN (line 37) | @NotNull
method getRegisteredMethods (line 40) | @NotNull
method getRegisteredCallInfos (line 47) | @NotNull
method getSignature (line 50) | @Nullable
method deleteSignature (line 53) | void deleteSignature(@NotNull MethodInfo method) throws StorageException;
method putSignature (line 55) | void putSignature(@NotNull SignatureInfo signatureInfo) throws Storage...
FILE: storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/RSignatureStorage.java
type RSignatureStorage (line 9) | public interface RSignatureStorage<T extends RSignatureStorage.Packet> e...
method readPacket (line 11) | default void readPacket(@NotNull T packet) throws StorageException {
method formPackets (line 26) | @NotNull
class ExportDescriptor (line 29) | class ExportDescriptor {
method ExportDescriptor (line 35) | public ExportDescriptor(boolean include, @NotNull Collection<GemInfo...
method isInclude (line 40) | public boolean isInclude() {
method getGemsToIncludeOrExclude (line 44) | @NotNull
type Packet (line 50) | interface Packet {
method getSignatures (line 51) | Collection<SignatureInfo> getSignatures();
FILE: storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/StorageException.java
class StorageException (line 3) | @SuppressWarnings("unused")
method StorageException (line 5) | public StorageException() {
method StorageException (line 8) | public StorageException(String message) {
method StorageException (line 12) | public StorageException(String message, Throwable cause) {
method StorageException (line 16) | public StorageException(Throwable cause) {
method StorageException (line 20) | public StorageException(String message, Throwable cause, boolean enabl...
Condensed preview — 160 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (450K chars).
[
{
"path": ".gitignore",
"chars": 90,
"preview": "/build/\nout/\n*/build/\n.gradle\n\n.idea/\n\n**/*.iml\n**/.rakeTasks\narg_scanner/arg_scanner.iml\n"
},
{
"path": ".travis.yml",
"chars": 1028,
"preview": "language: ruby\ndist: trusty\nos:\n - linux\n# - osx\n\nrvm:\n - 2.3.3\n - 2.4.2\n - ruby-head\n\nmatrix:\n fast_finish: true\n"
},
{
"path": "FEATURES.md",
"chars": 599,
"preview": "# ruby-type-inference features\n\nThis doc contains `ruby-type-inference` features which can be useful \nfor you after runn"
},
{
"path": "LICENSE",
"chars": 11351,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 5047,
"preview": "Automated Type Contracts Generation [](https://confluen"
},
{
"path": "arg_scanner/.gitignore",
"chars": 116,
"preview": "*.iml\n\n.bundle/\n.yardoc\nGemfile.lock\n_yardoc/\ncoverage/\ndoc/\npkg/\nspec/reports/\ntmp/\n*.bundle\n*.so\n*.o\n*.a\nmkmf.log\n"
},
{
"path": "arg_scanner/Gemfile",
"chars": 134,
"preview": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in arg_scanner.gemspec\ngemspec\n\ngroup :test do\n gem 't"
},
{
"path": "arg_scanner/LICENSE.txt",
"chars": 1076,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2017 JetBrains\n\nPermission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "arg_scanner/README.md",
"chars": 1939,
"preview": "# ArgScanner [](https://badge.fury.io/rb/arg_scanner)\n\n`arg_scan"
},
{
"path": "arg_scanner/Rakefile",
"chars": 486,
"preview": "require \"bundler/gem_tasks\"\nrequire \"rake/extensiontask\"\nrequire 'rake/testtask'\n\nBASE_TEST_FILE_LIST = Dir['test/**/tes"
},
{
"path": "arg_scanner/arg_scanner.gemspec",
"chars": 1584,
"preview": "# coding: utf-8\nlib = File.expand_path('../lib', __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequi"
},
{
"path": "arg_scanner/bin/arg-scanner",
"chars": 2403,
"preview": "#!/usr/bin/env ruby\n\nrequire 'optparse'\nrequire 'arg_scanner/options'\nrequire 'arg_scanner/version'\n\noptions = ArgScanne"
},
{
"path": "arg_scanner/bin/console",
"chars": 336,
"preview": "#!/usr/bin/env ruby\n\nrequire \"bundler/setup\"\nrequire \"arg_scanner\"\n\n# You can add fixtures and/or initialization code he"
},
{
"path": "arg_scanner/bin/rubymine-type-tracker",
"chars": 2146,
"preview": "#!/usr/bin/env ruby\n# This is small script for launching type tracker under RubyMine's provided server. Acts like arg-sc"
},
{
"path": "arg_scanner/bin/setup",
"chars": 131,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\nIFS=$'\\n\\t'\nset -vx\n\nbundle install\n\n# Do any other automated setup that you need "
},
{
"path": "arg_scanner/ext/arg_scanner/arg_scanner.c",
"chars": 27612,
"preview": "#include \"arg_scanner.h\"\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <asser"
},
{
"path": "arg_scanner/ext/arg_scanner/arg_scanner.h",
"chars": 173,
"preview": "#ifndef ARG_SCANNER_H\n#define ARG_SCANNER_H 1\n\n#include \"ruby.h\"\n#include \"vm_core.h\"\n#include \"version.h\"\n#include \"ise"
},
{
"path": "arg_scanner/ext/arg_scanner/extconf.rb",
"chars": 2428,
"preview": "require \"mkmf\"\n\nRbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']\n\nrequire \"debase/ruby_core_source\"\nrequire \"nat"
},
{
"path": "arg_scanner/lib/arg_scanner/options.rb",
"chars": 1027,
"preview": "require 'ostruct'\n\nmodule ArgScanner\n OPTIONS = OpenStruct.new(\n :enable_type_tracker => ENV['ARG_SCANNER_ENABLE_T"
},
{
"path": "arg_scanner/lib/arg_scanner/require_all.rb",
"chars": 10072,
"preview": "# Copyright (c) 2009 Jarmo Pertman\n\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of "
},
{
"path": "arg_scanner/lib/arg_scanner/starter.rb",
"chars": 469,
"preview": "# starter.rb is loaded with \"ruby -r\" option from bin/arg-scanner\n# or by IDEA also with \"ruby -r\" option\n\nunless ENV[\"A"
},
{
"path": "arg_scanner/lib/arg_scanner/state_tracker.rb",
"chars": 2657,
"preview": "require \"set\"\nrequire_relative \"require_all\"\nrequire_relative \"workspace\"\n\n\nmodule ArgScanner\n class StateTracker\n d"
},
{
"path": "arg_scanner/lib/arg_scanner/type_tracker.rb",
"chars": 2007,
"preview": "require 'set'\nrequire 'socket'\nrequire 'singleton'\nrequire 'thread'\n\nrequire_relative 'options'\n\nmodule ArgScanner\n\n cl"
},
{
"path": "arg_scanner/lib/arg_scanner/version.rb",
"chars": 42,
"preview": "module ArgScanner\n VERSION = \"0.3.3\"\nend\n"
},
{
"path": "arg_scanner/lib/arg_scanner/workspace.rb",
"chars": 620,
"preview": "module ArgScanner\n class Workspace\n\n def initialize\n @dir = ENV[\"ARG_SCANNER_DIR\"] || \".\"\n @pid_file = @di"
},
{
"path": "arg_scanner/lib/arg_scanner.rb",
"chars": 185,
"preview": "require \"arg_scanner/version\"\nrequire \"arg_scanner/arg_scanner\"\nrequire \"arg_scanner/type_tracker\"\nrequire \"arg_scanner/"
},
{
"path": "arg_scanner/test/helper.rb",
"chars": 674,
"preview": "$LOAD_PATH.unshift(File.dirname(__dir__) + '/../lib')\nrequire \"test-unit\"\nrequire \"arg_scanner\"\n\nclass TestTypeTracker\n "
},
{
"path": "arg_scanner/test/test_args_info.rb",
"chars": 3337,
"preview": "#!/usr/bin/env ruby\nrequire File.expand_path(\"helper\", File.dirname(__FILE__))\nrequire 'date'\n\nclass TestArgsInfoWrapper"
},
{
"path": "arg_scanner/test/test_call_info.rb",
"chars": 3050,
"preview": "#!/usr/bin/env ruby\nrequire File.expand_path(\"helper\", File.dirname(__FILE__))\n\nclass TestCallInfoWrapper\n\n def sqr(z1 "
},
{
"path": "arg_scanner/test/test_state_tracker.rb",
"chars": 2243,
"preview": "require 'test/unit'\nrequire 'tempfile'\nrequire 'fileutils'\nrequire 'json'\n\nclass StateTrackerTest < Test::Unit::TestCase"
},
{
"path": "arg_scanner/util/state_filter.rb",
"chars": 755,
"preview": "#!/usr/bin/env ruby\n\nrequire 'json'\nrequire 'set'\n\nif ARGV.length < 3\n puts(\"state_filter.rb <in-file> <out-file> [<lis"
},
{
"path": "build.gradle",
"chars": 1422,
"preview": "buildscript {\n repositories {\n jcenter()\n mavenCentral()\n }\n\n dependencies {\n classpath \"o"
},
{
"path": "common/build.gradle",
"chars": 328,
"preview": "buildscript {\n repositories {\n jcenter()\n }\n\n dependencies {\n classpath \"org.jetbrains.kotlin:kot"
},
{
"path": "common/src/main/java/org/jetbrains/ruby/codeInsight/Injector.kt",
"chars": 717,
"preview": "package org.jetbrains.ruby.codeInsight\n\n/**\n * Dependency injection mechanism\n */\ninterface Injector {\n fun <T> getLo"
},
{
"path": "common/src/main/java/org/jetbrains/ruby/codeInsight/Logger.kt",
"chars": 87,
"preview": "package org.jetbrains.ruby.codeInsight\n\ninterface Logger {\n fun info(msg: String)\n}\n"
},
{
"path": "common/src/main/java/org/jetbrains/ruby/codeInsight/PrintToStdoutLogger.kt",
"chars": 439,
"preview": "package org.jetbrains.ruby.codeInsight\n\nimport java.text.SimpleDateFormat\nimport java.util.*\n\nprivate val format = Simpl"
},
{
"path": "contract-creator/build.gradle",
"chars": 384,
"preview": "sourceSets {\n main.java.srcDirs = ['src']\n}\n\ndependencies {\n compile project(':common')\n compile project(':ruby"
},
{
"path": "contract-creator/src/org/jetbrains/ruby/runtime/signature/server/SignatureServer.kt",
"chars": 7840,
"preview": "package org.jetbrains.ruby.runtime.signature.server\n\nimport com.google.gson.Gson\nimport com.google.gson.JsonParseExcepti"
},
{
"path": "contract-creator/src/org/jetbrains/ruby/runtime/signature/server/SignatureServerInjector.kt",
"chars": 350,
"preview": "package org.jetbrains.ruby.runtime.signature.server\n\nimport org.jetbrains.ruby.codeInsight.Injector\nimport org.jetbrains"
},
{
"path": "contract-creator/src/org/jetbrains/ruby/runtime/signature/server/serialisation/ServerResponseBean.kt",
"chars": 4096,
"preview": "package org.jetbrains.ruby.runtime.signature.server.serialisation\n\nimport org.jetbrains.ruby.codeInsight.types.signature"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 233,
"preview": "#Wed Nov 07 19:25:40 MSK 2018\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle.properties",
"chars": 346,
"preview": "# Available idea versions:\n# https://www.jetbrains.com/intellij-repository/releases\n# https://www.jetbrains.com/intellij"
},
{
"path": "gradlew",
"chars": 5296,
"preview": "#!/usr/bin/env sh\n\n##############################################################################\n##\n## Gradle start up"
},
{
"path": "gradlew.bat",
"chars": 2260,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "ide-plugin/CHANGELOG.md",
"chars": 574,
"preview": "## 0.1.1 (15 Dec 2017)\n\n* (#17) Fix \"find usages\" action for dynamic symbols which resolve to text-based\n definitions.\n"
},
{
"path": "ide-plugin/build.gradle",
"chars": 1607,
"preview": "buildscript {\n repositories {\n maven { url 'https://dl.bintray.com/jetbrains/intellij-plugin-service' }\n }\n"
},
{
"path": "ide-plugin/resources/META-INF/plugin.xml",
"chars": 4736,
"preview": "<idea-plugin>\n <id>org.jetbrains.ruby-runtime-stats</id>\n <name>Ruby Dynamic Code Insight</name>\n <vendor email"
},
{
"path": "ide-plugin/src/com/intellij/execution/executors/CollectStateExecutor.kt",
"chars": 1659,
"preview": "package com.intellij.execution.executors\n\nimport com.intellij.execution.Executor\nimport com.intellij.icons.AllIcons\nimpo"
},
{
"path": "ide-plugin/src/com/intellij/execution/executors/RunWithTypeTrackerExecutor.java",
"chars": 2479,
"preview": "package com.intellij.execution.executors;\n\nimport com.intellij.execution.Executor;\nimport com.intellij.icons.AllIcons;\ni"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/IdePluginLogger.kt",
"chars": 277,
"preview": "package org.jetbrains.plugins.ruby\n\nimport org.jetbrains.ruby.codeInsight.Logger\n\nclass IdePluginLogger(private val inte"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/PluginResourceUtil.java",
"chars": 921,
"preview": "package org.jetbrains.plugins.ruby;\n\nimport com.intellij.ide.plugins.IdeaPluginDescriptor;\nimport com.intellij.ide.plugi"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/RubyDynamicCodeInsightPluginInjector.kt",
"chars": 336,
"preview": "package org.jetbrains.plugins.ruby\n\nimport org.jetbrains.ruby.codeInsight.Injector\nimport org.jetbrains.ruby.codeInsight"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ancestorsextractor/AncestorsExtractor.kt",
"chars": 6718,
"preview": "package org.jetbrains.plugins.ruby.ancestorsextractor\n\nimport com.intellij.openapi.application.ReadAction\nimport com.int"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ancestorsextractor/RailsConsoleRunner.kt",
"chars": 6963,
"preview": "package org.jetbrains.plugins.ruby.ancestorsextractor\n\nimport com.google.gson.Gson\nimport com.intellij.execution.Executi"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/actions/ExportAncestorsActions.kt",
"chars": 2420,
"preview": "package org.jetbrains.plugins.ruby.ruby.actions\n\nimport com.intellij.openapi.module.Module\nimport com.intellij.openapi.p"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/actions/ExportAncesttorsDiffAction.kt",
"chars": 3941,
"preview": "package org.jetbrains.plugins.ruby.ruby.actions\n\nimport com.intellij.openapi.module.Module\nimport com.intellij.openapi.p"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/actions/ExportFileActionBase.kt",
"chars": 4173,
"preview": "package org.jetbrains.plugins.ruby.ruby.actions\n\nimport com.intellij.openapi.actionSystem.AnActionEvent\nimport com.intel"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/actions/ImportExportContractsAction.kt",
"chars": 4304,
"preview": "package org.jetbrains.plugins.ruby.ruby.actions\n\nimport com.intellij.openapi.actionSystem.AnActionEvent\nimport com.intel"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/ProjectLifecycleListenerImpl.kt",
"chars": 3691,
"preview": "package org.jetbrains.plugins.ruby.ruby.codeInsight\n\nimport com.google.gson.Gson\nimport com.intellij.openapi.project.Pro"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/RubyDynamicCodeInsightPluginAppLifecyctlListener.kt",
"chars": 477,
"preview": "package org.jetbrains.plugins.ruby.ruby.codeInsight\n\nimport com.intellij.ide.AppLifecycleListener\nimport com.intellij.op"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/TrackerDataLoader.kt",
"chars": 593,
"preview": "package org.jetbrains.plugins.ruby.ruby.codeInsight\n\nimport com.intellij.openapi.module.ModuleManager\nimport com.intelli"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/stateTracker/ClassHierarchySymbolProvider.kt",
"chars": 1224,
"preview": "package org.jetbrains.plugins.ruby.ruby.codeInsight.stateTracker\n\nimport com.intellij.openapi.module.ModuleUtilCore\nimpo"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/stateTracker/RubyClassHierarchyWithCaching.kt",
"chars": 5903,
"preview": "package org.jetbrains.plugins.ruby.ruby.codeInsight.stateTracker\n\nimport com.intellij.openapi.components.ServiceManager\n"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/symbols/structure/RMethodSyntheticSymbol.java",
"chars": 7724,
"preview": "package org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure;\n\nimport com.intellij.openapi.application.ReadActi"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/types/RubyCollectStateRunner.kt",
"chars": 2304,
"preview": "package org.jetbrains.plugins.ruby.ruby.codeInsight.types\n\nimport com.intellij.execution.ExecutionException\nimport com.i"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/types/RubyRunWithTypeTrackerRunner.kt",
"chars": 2590,
"preview": "package org.jetbrains.plugins.ruby.ruby.codeInsight.types\n\nimport com.intellij.execution.ExecutionException\nimport com.i"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/codeInsight/types/RubyTypeProvider.kt",
"chars": 11297,
"preview": "package org.jetbrains.plugins.ruby.ruby.codeInsight.types\n\nimport com.intellij.openapi.application.ReadAction\nimport com"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/intentions/AddContractAnnotationIntention.java",
"chars": 6214,
"preview": "package org.jetbrains.plugins.ruby.ruby.intentions;\n\nimport com.intellij.openapi.editor.Editor;\nimport com.intellij.open"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/intentions/BaseRubyMethodIntentionAction.kt",
"chars": 1151,
"preview": "package org.jetbrains.plugins.ruby.ruby.intentions\n\nimport com.intellij.codeInsight.intention.impl.BaseIntentionAction\ni"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/intentions/RemoveCollectedInfoIntention.kt",
"chars": 1506,
"preview": "package org.jetbrains.plugins.ruby.ruby.intentions\n\nimport com.intellij.openapi.editor.Editor\nimport com.intellij.openap"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/persistent/TypeInferenceDirectory.kt",
"chars": 409,
"preview": "package org.jetbrains.plugins.ruby.ruby.persistent\n\nimport com.intellij.openapi.application.PathManager\nimport com.intel"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/run/configuration/CollectExecSettings.java",
"chars": 2685,
"preview": "package org.jetbrains.plugins.ruby.ruby.run.configuration;\n\nimport com.intellij.openapi.util.Key;\nimport org.jetbrains.a"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/ruby/run/configuration/RunWithTypeTrackerRunConfigurationExtension.java",
"chars": 10190,
"preview": "package org.jetbrains.plugins.ruby.ruby.run.configuration;\n\nimport com.intellij.execution.configurations.GeneralCommandL"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/settings/RubyTypeContractsConfigurable.kt",
"chars": 648,
"preview": "package org.jetbrains.plugins.ruby.settings\n\nimport com.intellij.openapi.options.ConfigurableBase\n\nclass RubyTypeContrac"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/settings/RubyTypeContractsConfigurableUI.kt",
"chars": 6292,
"preview": "package org.jetbrains.plugins.ruby.settings\n\nimport com.intellij.openapi.options.ConfigurableUi\nimport com.intellij.open"
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/settings/RubyTypeContractsSettings.kt",
"chars": 1649,
"preview": "package org.jetbrains.plugins.ruby.settings\n\nimport com.intellij.openapi.components.PersistentStateComponent\nimport com."
},
{
"path": "ide-plugin/src/org/jetbrains/plugins/ruby/util/SignatureServerUtil.kt",
"chars": 727,
"preview": "package org.jetbrains.plugins.ruby.util\n\nimport com.intellij.openapi.project.Project\nimport org.jetbrains.plugins.ruby.r"
},
{
"path": "ide-plugin/src/test/java/CallStatCompletionTest.kt",
"chars": 14343,
"preview": "import com.intellij.execution.ExecutionException\nimport com.intellij.openapi.diagnostic.Logger\nimport com.intellij.testF"
},
{
"path": "ide-plugin/src/test/java/org/jetbrains/plugins/ruby/ruby/actions/ImportExportTests.kt",
"chars": 4336,
"preview": "package org.jetbrains.plugins.ruby.ruby.actions\n\nimport junit.framework.Assert\nimport junit.framework.TestCase\nimport or"
},
{
"path": "ide-plugin/src/test/testData/anonymous_module_method_call_test.rb",
"chars": 71,
"preview": "module A\n def self.foo(a, b)\n true\n end\nend\n\nA.foo(\"hey\", :symbol)"
},
{
"path": "ide-plugin/src/test/testData/call_info_of_nested_class_test.rb",
"chars": 82,
"preview": "module M\n class A\n def foo(a)\n a\n end\n end\nend\n\na = M::A.new\na.foo(a)"
},
{
"path": "ide-plugin/src/test/testData/duplicates_in_callinfo_table_test.rb",
"chars": 131,
"preview": "def foo(a)\n if a == \"str\"\n return a\n end\n false\nend\n\nfoo(\"str\")\nfoo(\"not str\")\n3.times { foo(\"str\") }\n3.times { fo"
},
{
"path": "ide-plugin/src/test/testData/forget_call_info_when_arguments_number_changed_test_part_1.rb",
"chars": 69,
"preview": "class A\n def foo(a)\n :symbol\n end\nend\n\nA.new.foo(\"Hey\")\n"
},
{
"path": "ide-plugin/src/test/testData/forget_call_info_when_arguments_number_changed_test_part_2.rb",
"chars": 72,
"preview": "class A\n def foo(a, b)\n b\n end\nend\n\nA.new.foo(true, false)\n"
},
{
"path": "ide-plugin/src/test/testData/in_project_root_test/gem_like.rb",
"chars": 159,
"preview": "def catch(a); end\n\ndef dont_catch_2(a); end\n\ndef catch_2(a)\n dont_catch_2(a)\nend\n\ndef dont_catch_3(&a)\n yield(a)\nend\n\n"
},
{
"path": "ide-plugin/src/test/testData/in_project_root_test/in_project_root_test.rb",
"chars": 99,
"preview": "require_relative 'gem_like'\n\ncatch('hey')\n\ncatch_2('bro')\n\ndef foo(a); end\n\ncatch_3(&method(:foo))\n"
},
{
"path": "ide-plugin/src/test/testData/merge_test1.rb",
"chars": 246,
"preview": "class A\n\nend\n\nclass C\n\nend\n\nclass B1\n def test1\n\n end\n\n def test2\n\n end\nend\n\nclass B2\n def test3\n\n end\n\n def test"
},
{
"path": "ide-plugin/src/test/testData/merge_test1_to_run.rb",
"chars": 238,
"preview": "class A\n\nend\n\nclass C\n\nend\n\nclass B1\n def test1\n\n end\n\n def test2\n\n end\nend\n\nclass B2\n def test3\n\n end\n\n def test"
},
{
"path": "ide-plugin/src/test/testData/merge_test2.rb",
"chars": 246,
"preview": "class A\n\nend\n\nclass C\n\nend\n\nclass B1\n def test1\n\n end\n\n def test2\n\n end\nend\n\nclass B2\n def test3\n\n end\n\n def test"
},
{
"path": "ide-plugin/src/test/testData/merge_test2_to_run.rb",
"chars": 238,
"preview": "class A\n\nend\n\nclass C\n\nend\n\nclass B1\n def test1\n\n end\n\n def test2\n\n end\nend\n\nclass B2\n def test3\n\n end\n\n def test"
},
{
"path": "ide-plugin/src/test/testData/method_without_parameters_test.rb",
"chars": 24,
"preview": "def foo\n \"hey\"\nend\n\nfoo"
},
{
"path": "ide-plugin/src/test/testData/multiple_execution_test1.rb",
"chars": 177,
"preview": "require 'date'\n\nclass A\n\nend\n\nclass C\n\nend\n\nclass B\n def test1\n\n end\n\n def test2\n\n end\nend\n\nA.class_eval <<Foo\ndef f"
},
{
"path": "ide-plugin/src/test/testData/multiple_execution_test2.rb",
"chars": 194,
"preview": "require 'date'\n\nclass A\n\nend\n\nclass C\n\nend\n\nclass B\n def test1\n\n end\n\n def test2\n\n end\nend\n\nA.class_eval <<Foo\ndef f"
},
{
"path": "ide-plugin/src/test/testData/multiple_execution_test2_to_run.rb",
"chars": 187,
"preview": "require 'date'\n\nclass A\n\nend\n\nclass C\n\nend\n\nclass B\n def test1\n\n end\n\n def test2\n\n end\nend\n\nA.class_eval <<Foo\ndef f"
},
{
"path": "ide-plugin/src/test/testData/ref_links_test.rb",
"chars": 200,
"preview": "class A\n\nend\n\n\nclass B\n def test1\n\n end\n\n def test2\n\n end\nend\n\nA.class_eval <<Foo\ndef doo(a, b, c)\n a\nend\nFoo\n\nA.ne"
},
{
"path": "ide-plugin/src/test/testData/ref_links_test_to_run.rb",
"chars": 191,
"preview": "class A\n\nend\n\nclass B\n def test1\n\n end\n\n def test2\n\n end\nend\n\nA.class_eval <<Foo\ndef doo(a, b, c)\n a\nend\nFoo\n\nA.new"
},
{
"path": "ide-plugin/src/test/testData/ruby_exec_part_2.rb",
"chars": 27,
"preview": "def bar(a); end\n\nbar(true)\n"
},
{
"path": "ide-plugin/src/test/testData/ruby_exec_test.rb",
"chars": 111,
"preview": "def foo(a); end\n\nfoo(\"string\")\n\nKernel.exec(\"ruby\", \"#{File.expand_path(\"..\", __FILE__)}/ruby_exec_part_2.rb\")\n"
},
{
"path": "ide-plugin/src/test/testData/sample_kw_test.rb",
"chars": 189,
"preview": "require 'date'\n\nclass A\n\nend\n\nclass C\n\nend\n\nclass B\n def test1\n\n end\n\n def test2\n\n end\nend\n\nA.class_eval <<Foo\ndef f"
},
{
"path": "ide-plugin/src/test/testData/sample_kw_test_to_run.rb",
"chars": 181,
"preview": "require 'date'\n\nclass A\n\nend\n\nclass C\n\nend\n\nclass B\n def test1\n\n end\n\n def test2\n\n end\nend\n\nA.class_eval <<Foo\ndef f"
},
{
"path": "ide-plugin/src/test/testData/sample_test.rb",
"chars": 171,
"preview": "require 'date'\n\nclass A\n\nend\n\nclass C\n\nend\n\nclass B\n def test1\n\n end\n\n def test2\n\n end\nend\n\nA.class_eval <<Foo\ndef f"
},
{
"path": "ide-plugin/src/test/testData/sample_test_to_run.rb",
"chars": 163,
"preview": "require 'date'\n\nclass A\n\nend\n\nclass C\n\nend\n\nclass B\n def test1\n\n end\n\n def test2\n\n end\nend\n\nA.class_eval <<Foo\ndef f"
},
{
"path": "ide-plugin/src/test/testData/save_types_between_launches_test_part_1.rb",
"chars": 338,
"preview": "class A\n def foo(a)\n if a == \"str1\"\n return :symbol\n end\n if a == \"str2\"\n "
},
{
"path": "ide-plugin/src/test/testData/save_types_between_launches_test_part_2.rb",
"chars": 336,
"preview": "class A\n def foo(a)\n if a == \"str1\"\n return :symbol\n end\n if a == \"str2\"\n "
},
{
"path": "ide-plugin/src/test/testData/simple_call_info_collection_test.rb",
"chars": 157,
"preview": "class AClass\n def foo(a)\n if a.kind_of? String\n :symbol\n else\n true\n end\n "
},
{
"path": "ide-plugin/src/test/testData/simple_call_info_collection_test_multiple_functions_test.rb",
"chars": 170,
"preview": "class A\n def foo(a, b)\n a || b\n end\n\n def bar(a)\n a && A.new\n end\nend\n\na = A.new\n\na.foo(\"Hey\","
},
{
"path": "ide-plugin/src/test/testData/simple_call_info_collection_with_multiple_arguments_test.rb",
"chars": 96,
"preview": "class AClass\n def foo(a, b)\n /some regex/\n end\nend\n\nAClass.new.foo(\"String\", true)\n"
},
{
"path": "ide-plugin/src/test/testData/top_level_methods_call_info_collection_test.rb",
"chars": 156,
"preview": "def foo(a, b)\n if a == \"str\"\n return /some regex/\n end\n a || b\nend\n\nfoo(true, false)\nfoo(false, :symbol)\nf"
},
{
"path": "ruby-call-signature/build.gradle",
"chars": 120,
"preview": "apply plugin: 'java'\n\nsourceSets {\n main.java.srcDirs = ['src/main/java']\n test.java.srcDirs = ['src/test/java']\n}"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/CallInfo.kt",
"chars": 2944,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\nconst val ARGUMENTS_TYPES_SEPARATOR = \";\"\n\ninterface CallInfo {\n"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/ClassInfo.kt",
"chars": 767,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\n\ninterface ClassInfo {\n val gemInfo: GemInfo?\n val classFQ"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/GemInfo.kt",
"chars": 1052,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\ninterface GemInfo {\n val name: String\n val version: String"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/MethodInfo.kt",
"chars": 1346,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\ninterface MethodInfo {\n val classInfo: ClassInfo\n val name"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/ParameterInfo.java",
"chars": 1722,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature;\n\nimport org.jetbrains.annotations.NotNull;\n\npublic class Paramet"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/RSignatureContract.java",
"chars": 14553,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature;\n\nimport kotlin.Pair;\nimport org.jetbrains.annotations.NotNull;\ni"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/RSignatureContractContainer.kt",
"chars": 1335,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\nclass RSignatureContractContainer {\n\n private val myContracts"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/RSignatureContractNode.java",
"chars": 779,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains."
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/RTuple.java",
"chars": 1713,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Lis"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/SignatureContract.kt",
"chars": 7414,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\nimport org.jetbrains.ruby.codeInsight.types.signature.contractTr"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/SignatureInfo.kt",
"chars": 491,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\ninterface SignatureInfo {\n val methodInfo: MethodInfo\n val"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/contractTransition/ContractTransition.java",
"chars": 609,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature.contractTransition;\n\nimport org.jetbrains.annotations.NotNull;\n\ni"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/contractTransition/ReferenceContractTransition.java",
"chars": 1326,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature.contractTransition;\n\nimport org.jetbrains.annotations.NotNull;\n\ni"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/contractTransition/TransitionHelper.java",
"chars": 922,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature.contractTransition;\n\nimport org.jetbrains.annotations.NotNull;\n\ni"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/contractTransition/TypedContractTransition.java",
"chars": 989,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature.contractTransition;\n\nimport org.jetbrains.annotations.NotNull;\n\ni"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/serialization/MethodInfoSerialization.kt",
"chars": 6583,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature.serialization\n\nimport org.jetbrains.ruby.codeInsight.types.signat"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/serialization/RmcDirectory.kt",
"chars": 2040,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature.serialization\n\nimport org.jetbrains.ruby.codeInsight.types.signat"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/serialization/SignatureContractSerialization.kt",
"chars": 3170,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature.serialization\n\nimport org.jetbrains.ruby.codeInsight.types.signat"
},
{
"path": "ruby-call-signature/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/serialization/TestSerialization.kt",
"chars": 2638,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature.serialization\n\nimport java.io.DataInput\nimport java.io.DataOutput"
},
{
"path": "ruby-call-signature/src/test/java/org/jetbrains/ruby/codeInsight/types/signature/GemInfoFromPathTest.kt",
"chars": 1355,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\nimport junit.framework.TestCase\nimport org.junit.Test\n\nclass Gem"
},
{
"path": "ruby-call-signature/src/test/java/org/jetbrains/ruby/codeInsight/types/signature/SignatureContractMergeTest.kt",
"chars": 1761,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\nimport org.junit.Test\n\nclass SignatureContractMergeTest : Signat"
},
{
"path": "ruby-call-signature/src/test/java/org/jetbrains/ruby/codeInsight/types/signature/SignatureContractSerializationTest.kt",
"chars": 3754,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\nimport org.jetbrains.ruby.codeInsight.types.signature.serializat"
},
{
"path": "ruby-call-signature/src/test/java/org/jetbrains/ruby/codeInsight/types/signature/SignatureContractTestBase.kt",
"chars": 5192,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature\n\nimport junit.framework.TestCase\nimport org.jetbrains.ruby.codeIn"
},
{
"path": "settings.gradle",
"chars": 216,
"preview": "include 'ruby-call-signature', 'storage-server-api', 'lambda-update-handler', 'lambda-put-handler'\ninclude 'contract-cre"
},
{
"path": "signature-viewer/build.gradle",
"chars": 1074,
"preview": "version 'unspecified'\n\napply plugin: 'java'\n\nsourceCompatibility = 1.8\n\nrepositories {\n mavenCentral()\n}\n\n\ndependenci"
},
{
"path": "signature-viewer/src/org/jetbrains/ruby/runtime/signature/DBViewer.kt",
"chars": 1445,
"preview": "package org.jetbrains.ruby.runtime.signature\n\nimport org.jetbrains.exposed.sql.transactions.transaction\nimport org.jetbr"
},
{
"path": "signature-viewer/src/org/jetbrains/ruby/runtime/signature/EraseLocation.kt",
"chars": 1018,
"preview": "package org.jetbrains.ruby.runtime.signature\n\nimport org.jetbrains.exposed.sql.transactions.transaction\nimport org.jetbr"
},
{
"path": "signature-viewer/src/org/jetbrains/ruby/runtime/signature/SignatureExport.kt",
"chars": 1559,
"preview": "package org.jetbrains.ruby.runtime.signature\n\nimport org.jetbrains.ruby.codeInsight.types.signature.SignatureInfo\nimport"
},
{
"path": "signature-viewer/src/org/jetbrains/ruby/runtime/signature/SignatureImport.kt",
"chars": 1030,
"preview": "package org.jetbrains.ruby.runtime.signature\n\nimport org.jetbrains.ruby.codeInsight.types.signature.serialization.RmcDir"
},
{
"path": "signature-viewer/src/org/jetbrains/ruby/runtime/signature/SignatureViewer.kt",
"chars": 2186,
"preview": "package org.jetbrains.ruby.runtime.signature\n\nimport org.jetbrains.ruby.codeInsight.types.signature.SignatureContract\nim"
},
{
"path": "signature-viewer/src/org/jetbrains/ruby/runtime/signature/SplitDB.kt",
"chars": 1629,
"preview": "package org.jetbrains.ruby.runtime.signature\n\nimport org.jetbrains.exposed.sql.Database\nimport org.jetbrains.exposed.sql"
},
{
"path": "state-tracker/build.gradle",
"chars": 751,
"preview": "buildscript {\n ext.kotlin_version = '1.2.70'\n\n repositories {\n mavenCentral()\n }\n dependencies {\n "
},
{
"path": "state-tracker/src/main/java/org/jetbrains/ruby/stateTracker/RubyClassHierarchy.kt",
"chars": 3501,
"preview": "package org.jetbrains.ruby.stateTracker\n\ninterface RubyClassHierarchy {\n val loadPaths: List<String>\n\n val topLeve"
},
{
"path": "state-tracker/src/main/java/org/jetbrains/ruby/stateTracker/RubyClassHierarchyLoader.kt",
"chars": 7412,
"preview": "package org.jetbrains.ruby.stateTracker\n\nimport com.google.gson.Gson\nimport java.util.*\nimport kotlin.collections.ArrayL"
},
{
"path": "state-tracker/src/test/java/org/jetbrains/ruby/stateTracker/RubyClassHierarchyLoaderNonStandardModuleTypeTest.kt",
"chars": 744,
"preview": "package org.jetbrains.ruby.stateTracker\n\nimport junit.framework.TestCase\nimport org.junit.Test\n\nclass RubyClassHierarchy"
},
{
"path": "state-tracker/src/test/java/org/jetbrains/ruby/stateTracker/RubyClassHierarchyLoaderTest.kt",
"chars": 3188,
"preview": "package org.jetbrains.ruby.stateTracker\n\nimport junit.framework.TestCase\nimport org.junit.Test\n\nclass RubyClassHierarchy"
},
{
"path": "state-tracker/src/test/java/testData/classes.json",
"chars": 38616,
"preview": "{\n \"top_level_constants\": [\n {\n \"class_name\": \"IO\",\n \"extended\": [],\n \"name\": \"STDIN\"\n }\n ],\n\n \""
},
{
"path": "state-tracker/src/test/java/testData/non-standard-module-type.json",
"chars": 672,
"preview": "{\n \"top_level_constants\": [\n {\n \"class_name\": \"IO\",\n \"extended\": [],\n \"name\": \"STDIN\"\n }\n ],\n \"l"
},
{
"path": "storage-server-api/build.gradle",
"chars": 503,
"preview": "buildscript {\n repositories {\n jcenter()\n }\n\n dependencies {\n classpath \"org.jetbrains.kotlin:kot"
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/signature/serialization/BlobSerialization.kt",
"chars": 1965,
"preview": "package org.jetbrains.ruby.codeInsight.types.signature.serialization\n\nimport org.jetbrains.exposed.dao.EntityHook\nimport"
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/DatabaseProvider.kt",
"chars": 3054,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server\n\nimport org.jetbrains.exposed.sql.Database\nimport org.jetbra"
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/RSignatureProvider.java",
"chars": 2372,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbr"
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/RSignatureStorage.java",
"chars": 1805,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbr"
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/StorageException.java",
"chars": 629,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server;\n\n@SuppressWarnings(\"unused\")\npublic class StorageException "
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/impl/IntIdTableWithPossibleDependency.kt",
"chars": 6026,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server.impl\n\nimport org.jetbrains.exposed.dao.EntityID\nimport org.j"
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/impl/RSignatureProviderImpl.kt",
"chars": 4964,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server.impl\n\nimport org.jetbrains.exposed.sql.and\nimport org.jetbra"
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/impl/RowConversions.kt",
"chars": 1111,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server.impl\n\nimport org.jetbrains.exposed.sql.ResultRow\nimport org."
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/impl/Schema.kt",
"chars": 12400,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server.impl\n\nimport org.jetbrains.exposed.dao.EntityID\nimport org.j"
},
{
"path": "storage-server-api/src/main/java/org/jetbrains/ruby/codeInsight/types/storage/server/testutil/DatabaseTestUtils.kt",
"chars": 708,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server.testutil\n\nimport org.jetbrains.exposed.sql.transactions.tran"
},
{
"path": "storage-server-api/src/test/java/org/jetbrains/ruby/codeInsight/types/storage/server/impl/RSignatureProviderTest.kt",
"chars": 9587,
"preview": "package org.jetbrains.ruby.codeInsight.types.storage.server.impl\n\nimport junit.framework.TestCase\nimport org.jetbrains.e"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the JetBrains/ruby-type-inference GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 160 files (406.4 KB), approximately 101.2k tokens, and a symbol index with 380 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.