├── .gitignore ├── .travis.yml ├── CHANGELOG ├── Dockerfile ├── LICENSE ├── README.md ├── integration └── todo │ ├── .gitignore │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── Rakefile │ ├── app │ ├── assets │ │ ├── config │ │ │ └── manifest.js │ │ ├── images │ │ │ └── .keep │ │ ├── javascripts │ │ │ ├── application.js │ │ │ ├── cable.js │ │ │ ├── channels │ │ │ │ └── .keep │ │ │ ├── todo_items.coffee │ │ │ └── todo_lists.coffee │ │ └── stylesheets │ │ │ ├── application.css │ │ │ ├── scaffolds.scss │ │ │ ├── todo_items.scss │ │ │ └── todo_lists.scss │ ├── channels │ │ └── application_cable │ │ │ ├── channel.rb │ │ │ └── connection.rb │ ├── controllers │ │ ├── application_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── todo_items_controller.rb │ │ └── todo_lists_controller.rb │ ├── helpers │ │ ├── application_helper.rb │ │ ├── todo_items_helper.rb │ │ └── todo_lists_helper.rb │ ├── jobs │ │ └── application_job.rb │ ├── mailers │ │ └── application_mailer.rb │ ├── models │ │ ├── application_record.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── todo_item.rb │ │ └── todo_list.rb │ └── views │ │ ├── layouts │ │ ├── application.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb │ │ ├── todo_items │ │ ├── _form.html.erb │ │ └── _todo_item.html.erb │ │ └── todo_lists │ │ ├── _form.html.erb │ │ ├── _todo_list.json.jbuilder │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── bin │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ ├── spring │ ├── update │ └── yarn │ ├── config.ru │ ├── config │ ├── application.rb │ ├── boot.rb │ ├── cable.yml │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── application_controller_renderer.rb │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── puma.rb │ ├── routes.rb │ ├── secrets.yml │ └── spring.rb │ ├── db │ ├── migrate │ │ ├── 20180818210306_create_todo_lists.rb │ │ ├── 20180819121311_create_todo_items.rb │ │ └── 20180819123754_add_completed_at_to_todo_items.rb │ ├── schema.rb │ └── seeds.rb │ ├── lib │ ├── assets │ │ └── .keep │ └── tasks │ │ └── .keep │ ├── log │ └── .keep │ ├── package.json │ ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ ├── favicon.ico │ └── robots.txt │ ├── sonar-project.properties │ ├── test │ ├── application_system_test_case.rb │ ├── controllers │ │ ├── .keep │ │ ├── todo_items_controller_test.rb │ │ └── todo_lists_controller_test.rb │ ├── fixtures │ │ ├── .keep │ │ ├── files │ │ │ └── .keep │ │ ├── todo_items.yml │ │ └── todo_lists.yml │ ├── helpers │ │ └── .keep │ ├── integration │ │ └── .keep │ ├── mailers │ │ └── .keep │ ├── models │ │ ├── .keep │ │ ├── todo_item_test.rb │ │ └── todo_list_test.rb │ ├── system │ │ ├── .keep │ │ └── todo_lists_test.rb │ └── test_helper.rb │ ├── tmp │ └── .keep │ └── vendor │ └── .keep ├── pom.xml ├── sonar-project.properties └── src ├── main ├── java │ └── com │ │ └── fortitudetec │ │ └── sonar │ │ └── plugins │ │ └── ruby │ │ ├── PathResolver.java │ │ ├── Ruby.java │ │ ├── RubyPlugin.java │ │ ├── RubyRuleProfile.java │ │ ├── RubyRulesDefinition.java │ │ ├── metrics │ │ ├── ClassCountParser.java │ │ ├── CombinedCoverageSensor.java │ │ ├── CommentCountParser.java │ │ └── RubyMetricsSensor.java │ │ ├── model │ │ ├── RubocopIssue.java │ │ ├── RubocopPosition.java │ │ └── RubocopRule.java │ │ ├── rubocop │ │ ├── RubocopExecutor.java │ │ ├── RubocopExecutorConfig.java │ │ ├── RubocopParser.java │ │ ├── RubocopSensor.java │ │ └── model │ │ │ ├── RubocopFile.java │ │ │ ├── RubocopOffense.java │ │ │ └── RubocopResult.java │ │ ├── simplecov │ │ ├── SimpleCovParser.java │ │ └── SimpleCovSensor.java │ │ └── squid │ │ ├── RubyFootPrint.java │ │ └── RubyRecognizer.java └── resources │ └── rubocop │ └── rubocop.yml └── test ├── java └── com │ └── fortitudetec │ └── sonar │ └── plugins │ └── ruby │ ├── PathResolverTest.java │ ├── RubyPluginTest.java │ ├── RubyRuleProfileTest.java │ ├── RubyRulesDefinitionTest.java │ ├── RubyTest.java │ ├── metrics │ ├── ClassCountParserTest.java │ ├── CombinedCoverageSensorTest.java │ ├── CommentCountParserTest.java │ └── RubyMetricsSensorTest.java │ ├── rubocop │ ├── RubocopParserTest.java │ └── RubocopSensorTest.java │ ├── simplecov │ ├── SimpleCovParserTest.java │ └── SimpleCovSensorTest.java │ └── utils │ └── TestUtils.java └── resources ├── result.json ├── test_controller.rb └── unknown_file.rb /.gitignore: -------------------------------------------------------------------------------- 1 | ### Maven template 2 | target/ 3 | 4 | ### Java template 5 | # Log file 6 | *.log 7 | 8 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 9 | hs_err_pid* 10 | ### JetBrains template 11 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 12 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 13 | 14 | # User-specific stuff: 15 | .idea/ 16 | *.iml 17 | 18 | ## File-based project format: 19 | *.iws 20 | 21 | ## Plugin-specific files: 22 | 23 | # IntelliJ 24 | /out/ 25 | 26 | # mpeltonen/sbt-idea plugin 27 | .idea_modules/ 28 | 29 | # Crashlytics plugin (for Android Studio and IntelliJ) 30 | com_crashlytics_export_strings.xml 31 | crashlytics.properties 32 | crashlytics-build.properties 33 | fabric.properties 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # https://docs.travis-ci.com/user/sonarcloud/ 2 | 3 | language: java 4 | 5 | addons: 6 | sonarcloud: 7 | organization: fortitudetec 8 | token: 9 | secure: 527af96a234edc72a58c766b3ef7ccc1d2159018 10 | 11 | script: 12 | # builds the project 13 | # runs tests with coverage 14 | # execute SonarCloud analysis 15 | - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar 16 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v 1.1.0: 2 | - Support other test runners besides RSpec 3 | - Added a sample Rails application for testing the plugin with SonarQube 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM sonarqube:7.1 2 | 3 | COPY target/sonar-ruby-plugin-1.1.0.jar /opt/sonarqube/extensions/plugins/ 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Fortitude Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## This project is no longer maintained and is archived 2 | 3 | # Sonar Ruby Plugin 4 | SonarQube plugin for analyzing Ruby files 5 | 6 | ![SonarCloud](https://sonarcloud.io/api/project_badges/measure?project=com.fortitudetec.sonar%3Asonar-ruby-plugin&metric=alert_status) 7 | [![Build Status](https://travis-ci.com/fortitudetec/sonar-ruby-plugin.svg?branch=master)](https://travis-ci.com/fortitudetec/sonar-ruby-plugin) 8 | 9 | ## Requirements 10 | * Rubocop (https://github.com/bbatsov/rubocop) 11 | * Simplecov (https://github.com/colszowka/simplecov) 12 | * Tested with v6.5, v6.6, v7.1 13 | 14 | ## Overview 15 | This is plugin for SonarQube 5.6+ for analysing projects with Ruby content that supports: 16 | 17 | * Rubocop for code quality information 18 | * Simplecov for unit test coverage information 19 | * NCLOC metric generation 20 | 21 | ## Configuration 22 | 23 | ### Example project configuration 24 | This is an example of what a project configuration file (`sonar-project.properties`) could look like: 25 | ``` 26 | sonar.projectKey=company:my-application 27 | sonar.projectName=My Application 28 | sonar.projectVersion=1.0 29 | sonar.sourceEncoding=UTF-8 30 | sonar.sources=src/app 31 | sonar.exclusions=**/node_modules/**,**/*.spec.ts 32 | sonar.tests=src/app 33 | sonar.test.inclusions=**/*.spec.ts 34 | 35 | sonar.ruby.file.suffixes=rb,ruby 36 | sonar.ruby.coverage.reportPath=coverage/.resultset.json 37 | sonar.ruby.coverage.framework=RSpec 38 | sonar.ruby.rubocopConfig=.rubocop.yml 39 | sonar.ruby.rubocop=/usr/bin/rubocop 40 | sonar.ruby.rubocop.reportPath=rubocop-result.json 41 | sonar.ruby.rubocop.filePath=. 42 | ``` 43 | 44 | ## Installation 45 | 46 | ### Manual Installation 47 | Here are the steps to manually install this plugin for use in SonarQube: 48 | 49 | 1. Download the plugin jar 50 | 2. Copy the jar into the SonarQube plugin directory (e.g. /opt/sonarqube/extensions/plugins) 51 | 3. Restart SonarQube 52 | 53 | ### Update Center Installation 54 | TODO: Need to get this plugin added to SonarQube's update center 55 | 56 | ## Running the analysis 57 | In order to run the analysis for Ruby you will need to utilize the [sonar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) application. 58 | 59 | * Run rubocop making sure that the json results file is created 60 | e.g. `rubocop --format=json --out=rubocop-result.json` 61 | 62 | * Run your specs (make sure that your sonarqube config points to the right coverage directory ) 63 | e.g. `bundle exec rspec spec` 64 | *Make sure that your sonarqube config points to the right coverage directory e.g. `spec/coverage/.resultset.json`* 65 | 66 | * Make sure you have a sonar-project.properties file in the root of your project directory 67 | * run `sonar-scanner` 68 | 69 | ## Contributing 70 | #### 1. GitHub issue 71 | To request a new feature, [create a GitHub issue](https://github.com/fortitudetec/sonar-ruby-plugin/issues/new). Even if you plan to implement it yourself and submit it back to the community, please create an issue to be sure that we can follow up on it. 72 | 73 | #### 2. Pull Request 74 | To submit a contribution, create a pull request for this repository. 75 | 76 | ## License 77 | 78 | Copyright 2017-2018 Fortitude Technologies. 79 | 80 | Licensed under the [MIT License](https://en.wikipedia.org/wiki/MIT_License) 81 | -------------------------------------------------------------------------------- /integration/todo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | /node_modules 21 | /yarn-error.log 22 | 23 | .byebug_history 24 | 25 | .idea 26 | coverage 27 | -------------------------------------------------------------------------------- /integration/todo/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | git_source(:github) do |repo_name| 4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 5 | "https://github.com/#{repo_name}.git" 6 | end 7 | 8 | 9 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 10 | gem 'rails', '~> 5.2.1' 11 | # Use sqlite3 as the database for Active Record 12 | gem 'sqlite3' 13 | # Use Puma as the app server 14 | gem 'puma' 15 | # Use SCSS for stylesheets 16 | gem 'sass-rails' 17 | # Use Uglifier as compressor for JavaScript assets 18 | gem 'uglifier', '>= 1.3.0' 19 | 20 | # Use CoffeeScript for .coffee assets and views 21 | gem 'coffee-rails' 22 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 23 | gem 'turbolinks' 24 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 25 | gem 'jbuilder' 26 | 27 | # Use Capistrano for deployment 28 | # gem 'capistrano-rails', group: :development 29 | 30 | group :development, :test do 31 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 32 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] 33 | # Adds support for Capybara system testing and selenium driver 34 | gem 'capybara' 35 | gem 'minitest' 36 | gem 'minitest-reporters' 37 | gem 'selenium-webdriver' 38 | gem 'simplecov' 39 | end 40 | 41 | group :development do 42 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. 43 | gem 'web-console', '>= 3.3.0' 44 | gem 'listen', '>= 3.0.5', '< 3.2' 45 | gem 'rubocop' 46 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 47 | gem 'spring' 48 | gem 'spring-watcher-listen' 49 | end 50 | 51 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 52 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 53 | -------------------------------------------------------------------------------- /integration/todo/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.2.1) 5 | actionpack (= 5.2.1) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailer (5.2.1) 9 | actionpack (= 5.2.1) 10 | actionview (= 5.2.1) 11 | activejob (= 5.2.1) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.2.1) 15 | actionview (= 5.2.1) 16 | activesupport (= 5.2.1) 17 | rack (~> 2.0) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.2.1) 22 | activesupport (= 5.2.1) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.2.1) 28 | activesupport (= 5.2.1) 29 | globalid (>= 0.3.6) 30 | activemodel (5.2.1) 31 | activesupport (= 5.2.1) 32 | activerecord (5.2.1) 33 | activemodel (= 5.2.1) 34 | activesupport (= 5.2.1) 35 | arel (>= 9.0) 36 | activestorage (5.2.1) 37 | actionpack (= 5.2.1) 38 | activerecord (= 5.2.1) 39 | marcel (~> 0.3.1) 40 | activesupport (5.2.1) 41 | concurrent-ruby (~> 1.0, >= 1.0.2) 42 | i18n (>= 0.7, < 2) 43 | minitest (~> 5.1) 44 | tzinfo (~> 1.1) 45 | addressable (2.5.2) 46 | public_suffix (>= 2.0.2, < 4.0) 47 | ansi (1.5.0) 48 | arel (9.0.0) 49 | ast (2.4.0) 50 | bindex (0.5.0) 51 | builder (3.2.3) 52 | byebug (10.0.2) 53 | capybara (3.6.0) 54 | addressable 55 | mini_mime (>= 0.1.3) 56 | nokogiri (~> 1.8) 57 | rack (>= 1.6.0) 58 | rack-test (>= 0.6.3) 59 | xpath (~> 3.1) 60 | childprocess (0.9.0) 61 | ffi (~> 1.0, >= 1.0.11) 62 | coffee-rails (4.2.2) 63 | coffee-script (>= 2.2.0) 64 | railties (>= 4.0.0) 65 | coffee-script (2.4.1) 66 | coffee-script-source 67 | execjs 68 | coffee-script-source (1.12.2) 69 | concurrent-ruby (1.0.5) 70 | crass (1.0.4) 71 | docile (1.3.1) 72 | erubi (1.7.1) 73 | execjs (2.7.0) 74 | ffi (1.9.25) 75 | globalid (0.4.1) 76 | activesupport (>= 4.2.0) 77 | i18n (1.1.0) 78 | concurrent-ruby (~> 1.0) 79 | jaro_winkler (1.5.1) 80 | jbuilder (2.7.0) 81 | activesupport (>= 4.2.0) 82 | multi_json (>= 1.2) 83 | json (2.1.0) 84 | listen (3.1.5) 85 | rb-fsevent (~> 0.9, >= 0.9.4) 86 | rb-inotify (~> 0.9, >= 0.9.7) 87 | ruby_dep (~> 1.2) 88 | loofah (2.2.2) 89 | crass (~> 1.0.2) 90 | nokogiri (>= 1.5.9) 91 | mail (2.7.0) 92 | mini_mime (>= 0.1.1) 93 | marcel (0.3.2) 94 | mimemagic (~> 0.3.2) 95 | method_source (0.9.0) 96 | mimemagic (0.3.2) 97 | mini_mime (1.0.1) 98 | mini_portile2 (2.3.0) 99 | minitest (5.11.3) 100 | minitest-reporters (1.3.2) 101 | ansi 102 | builder 103 | minitest (>= 5.0) 104 | ruby-progressbar 105 | multi_json (1.13.1) 106 | nio4r (2.3.1) 107 | nokogiri (1.8.4) 108 | mini_portile2 (~> 2.3.0) 109 | parallel (1.12.1) 110 | parser (2.5.1.2) 111 | ast (~> 2.4.0) 112 | powerpack (0.1.2) 113 | public_suffix (3.0.3) 114 | puma (3.12.0) 115 | rack (2.0.5) 116 | rack-test (1.1.0) 117 | rack (>= 1.0, < 3) 118 | rails (5.2.1) 119 | actioncable (= 5.2.1) 120 | actionmailer (= 5.2.1) 121 | actionpack (= 5.2.1) 122 | actionview (= 5.2.1) 123 | activejob (= 5.2.1) 124 | activemodel (= 5.2.1) 125 | activerecord (= 5.2.1) 126 | activestorage (= 5.2.1) 127 | activesupport (= 5.2.1) 128 | bundler (>= 1.3.0) 129 | railties (= 5.2.1) 130 | sprockets-rails (>= 2.0.0) 131 | rails-dom-testing (2.0.3) 132 | activesupport (>= 4.2.0) 133 | nokogiri (>= 1.6) 134 | rails-html-sanitizer (1.0.4) 135 | loofah (~> 2.2, >= 2.2.2) 136 | railties (5.2.1) 137 | actionpack (= 5.2.1) 138 | activesupport (= 5.2.1) 139 | method_source 140 | rake (>= 0.8.7) 141 | thor (>= 0.19.0, < 2.0) 142 | rainbow (3.0.0) 143 | rake (12.3.1) 144 | rb-fsevent (0.10.3) 145 | rb-inotify (0.9.10) 146 | ffi (>= 0.5.0, < 2) 147 | rubocop (0.58.2) 148 | jaro_winkler (~> 1.5.1) 149 | parallel (~> 1.10) 150 | parser (>= 2.5, != 2.5.1.1) 151 | powerpack (~> 0.1) 152 | rainbow (>= 2.2.2, < 4.0) 153 | ruby-progressbar (~> 1.7) 154 | unicode-display_width (~> 1.0, >= 1.0.1) 155 | ruby-progressbar (1.10.0) 156 | ruby_dep (1.5.0) 157 | rubyzip (1.2.1) 158 | sass (3.5.7) 159 | sass-listen (~> 4.0.0) 160 | sass-listen (4.0.0) 161 | rb-fsevent (~> 0.9, >= 0.9.4) 162 | rb-inotify (~> 0.9, >= 0.9.7) 163 | sass-rails (5.0.7) 164 | railties (>= 4.0.0, < 6) 165 | sass (~> 3.1) 166 | sprockets (>= 2.8, < 4.0) 167 | sprockets-rails (>= 2.0, < 4.0) 168 | tilt (>= 1.1, < 3) 169 | selenium-webdriver (3.14.0) 170 | childprocess (~> 0.5) 171 | rubyzip (~> 1.2) 172 | simplecov (0.16.1) 173 | docile (~> 1.1) 174 | json (>= 1.8, < 3) 175 | simplecov-html (~> 0.10.0) 176 | simplecov-html (0.10.2) 177 | spring (2.0.2) 178 | activesupport (>= 4.2) 179 | spring-watcher-listen (2.0.1) 180 | listen (>= 2.7, < 4.0) 181 | spring (>= 1.2, < 3.0) 182 | sprockets (3.7.2) 183 | concurrent-ruby (~> 1.0) 184 | rack (> 1, < 3) 185 | sprockets-rails (3.2.1) 186 | actionpack (>= 4.0) 187 | activesupport (>= 4.0) 188 | sprockets (>= 3.0.0) 189 | sqlite3 (1.3.13) 190 | thor (0.20.0) 191 | thread_safe (0.3.6) 192 | tilt (2.0.8) 193 | turbolinks (5.1.1) 194 | turbolinks-source (~> 5.1) 195 | turbolinks-source (5.1.0) 196 | tzinfo (1.2.5) 197 | thread_safe (~> 0.1) 198 | uglifier (4.1.18) 199 | execjs (>= 0.3.0, < 3) 200 | unicode-display_width (1.4.0) 201 | web-console (3.6.2) 202 | actionview (>= 5.0) 203 | activemodel (>= 5.0) 204 | bindex (>= 0.4.0) 205 | railties (>= 5.0) 206 | websocket-driver (0.7.0) 207 | websocket-extensions (>= 0.1.0) 208 | websocket-extensions (0.1.3) 209 | xpath (3.1.0) 210 | nokogiri (~> 1.8) 211 | 212 | PLATFORMS 213 | ruby 214 | 215 | DEPENDENCIES 216 | byebug 217 | capybara 218 | coffee-rails 219 | jbuilder 220 | listen (>= 3.0.5, < 3.2) 221 | minitest 222 | minitest-reporters 223 | puma 224 | rails (~> 5.2.1) 225 | rubocop 226 | sass-rails 227 | selenium-webdriver 228 | simplecov 229 | spring 230 | spring-watcher-listen 231 | sqlite3 232 | turbolinks 233 | tzinfo-data 234 | uglifier (>= 1.3.0) 235 | web-console (>= 3.3.0) 236 | 237 | BUNDLED WITH 238 | 1.14.6 239 | -------------------------------------------------------------------------------- /integration/todo/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | -------------------------------------------------------------------------------- /integration/todo/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /integration/todo/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /integration/todo/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/app/assets/images/.keep -------------------------------------------------------------------------------- /integration/todo/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's 5 | // vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require rails-ujs 14 | //= require turbolinks 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /integration/todo/app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /integration/todo/app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /integration/todo/app/assets/javascripts/todo_items.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /integration/todo/app/assets/javascripts/todo_lists.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /integration/todo/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /integration/todo/app/assets/stylesheets/scaffolds.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | color: #333; 4 | margin: 33px; 5 | font-family: verdana, arial, helvetica, sans-serif; 6 | font-size: 13px; 7 | line-height: 18px; 8 | } 9 | 10 | p, ol, ul, td { 11 | font-family: verdana, arial, helvetica, sans-serif; 12 | font-size: 13px; 13 | line-height: 18px; 14 | } 15 | 16 | pre { 17 | background-color: #eee; 18 | padding: 10px; 19 | font-size: 11px; 20 | } 21 | 22 | a { 23 | color: #000; 24 | 25 | &:visited { 26 | color: #666; 27 | } 28 | 29 | &:hover { 30 | color: #fff; 31 | background-color: #000; 32 | } 33 | } 34 | 35 | th { 36 | padding-bottom: 5px; 37 | } 38 | 39 | td { 40 | padding: 0 5px 7px; 41 | } 42 | 43 | div { 44 | &.field, &.actions { 45 | margin-bottom: 10px; 46 | } 47 | } 48 | 49 | #notice { 50 | color: green; 51 | } 52 | 53 | .field_with_errors { 54 | padding: 2px; 55 | background-color: red; 56 | display: table; 57 | } 58 | 59 | #error_explanation { 60 | width: 450px; 61 | border: 2px solid red; 62 | padding: 7px 7px 0; 63 | margin-bottom: 20px; 64 | background-color: #f0f0f0; 65 | 66 | h2 { 67 | text-align: left; 68 | font-weight: bold; 69 | padding: 5px 5px 5px 15px; 70 | font-size: 12px; 71 | margin: -7px -7px 0; 72 | background-color: #c00; 73 | color: #fff; 74 | } 75 | 76 | ul li { 77 | font-size: 12px; 78 | list-style: square; 79 | } 80 | } 81 | 82 | label { 83 | display: block; 84 | } 85 | -------------------------------------------------------------------------------- /integration/todo/app/assets/stylesheets/todo_items.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the todo_items controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /integration/todo/app/assets/stylesheets/todo_lists.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the todo_lists controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /integration/todo/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /integration/todo/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /integration/todo/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | end 4 | -------------------------------------------------------------------------------- /integration/todo/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /integration/todo/app/controllers/todo_items_controller.rb: -------------------------------------------------------------------------------- 1 | class TodoItemsController < ApplicationController 2 | before_action :set_todo_list 3 | before_action :set_todo_item, except: [:create] 4 | 5 | def create 6 | @todo_item = @todo_list.todo_items.create(todo_item_params) 7 | redirect_to @todo_list 8 | end 9 | 10 | def destroy 11 | @todo_item = @todo_list.todo_items.find(params[:id]) 12 | if @todo_item.destroy 13 | flash[:success] = "Todo List item was deleted." 14 | else 15 | flash[:error] = "Todo List item could not be deleted." 16 | end 17 | redirect_to @todo_list 18 | end 19 | 20 | def complete 21 | @todo_item.update_attribute(:completed_at, Time.now) 22 | redirect_to @todo_list, notice: "Todo item completed" 23 | end 24 | 25 | private 26 | 27 | def set_todo_list 28 | @todo_list = TodoList.find(params[:todo_list_id]) 29 | end 30 | 31 | def set_todo_list_item 32 | @todo_item = @todo_list.todo_items.find(params[:id]) 33 | end 34 | 35 | def todo_item_params 36 | params[:todo_item].permit(:content) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /integration/todo/app/controllers/todo_lists_controller.rb: -------------------------------------------------------------------------------- 1 | class TodoListsController < ApplicationController 2 | before_action :set_todo_list, only: [:show, :edit, :update, :destroy] 3 | 4 | # GET /todo_lists 5 | # GET /todo_lists.json 6 | def index 7 | @todo_lists = TodoList.all 8 | end 9 | 10 | # GET /todo_lists/1 11 | # GET /todo_lists/1.json 12 | def show 13 | end 14 | 15 | # GET /todo_lists/new 16 | def new 17 | @todo_list = TodoList.new 18 | end 19 | 20 | # GET /todo_lists/1/edit 21 | def edit 22 | end 23 | 24 | # POST /todo_lists 25 | # POST /todo_lists.json 26 | def create 27 | @todo_list = TodoList.new(todo_list_params) 28 | 29 | respond_to do |format| 30 | if @todo_list.save 31 | format.html { redirect_to @todo_list, notice: 'Todo list was successfully created.' } 32 | format.json { render :show, status: :created, location: @todo_list } 33 | else 34 | format.html { render :new } 35 | format.json { render json: @todo_list.errors, status: :unprocessable_entity } 36 | end 37 | end 38 | end 39 | 40 | # PATCH/PUT /todo_lists/1 41 | # PATCH/PUT /todo_lists/1.json 42 | def update 43 | respond_to do |format| 44 | if @todo_list.update(todo_list_params) 45 | format.html { redirect_to @todo_list, notice: 'Todo list was successfully updated.' } 46 | format.json { render :show, status: :ok, location: @todo_list } 47 | else 48 | format.html { render :edit } 49 | format.json { render json: @todo_list.errors, status: :unprocessable_entity } 50 | end 51 | end 52 | end 53 | 54 | # DELETE /todo_lists/1 55 | # DELETE /todo_lists/1.json 56 | def destroy 57 | @todo_list.destroy 58 | respond_to do |format| 59 | format.html { redirect_to todo_lists_url, notice: 'Todo list was successfully destroyed.' } 60 | format.json { head :no_content } 61 | end 62 | end 63 | 64 | private 65 | # Use callbacks to share common setup or constraints between actions. 66 | def set_todo_list 67 | @todo_list = TodoList.find(params[:id]) 68 | end 69 | 70 | # Never trust parameters from the scary internet, only allow the white list through. 71 | def todo_list_params 72 | params.require(:todo_list).permit(:title, :description) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /integration/todo/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /integration/todo/app/helpers/todo_items_helper.rb: -------------------------------------------------------------------------------- 1 | module TodoItemsHelper 2 | end 3 | -------------------------------------------------------------------------------- /integration/todo/app/helpers/todo_lists_helper.rb: -------------------------------------------------------------------------------- 1 | module TodoListsHelper 2 | end 3 | -------------------------------------------------------------------------------- /integration/todo/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /integration/todo/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /integration/todo/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /integration/todo/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/app/models/concerns/.keep -------------------------------------------------------------------------------- /integration/todo/app/models/todo_item.rb: -------------------------------------------------------------------------------- 1 | class TodoItem < ApplicationRecord 2 | belongs_to :todo_list 3 | 4 | def completed? 5 | !completed_at.blank? 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /integration/todo/app/models/todo_list.rb: -------------------------------------------------------------------------------- 1 | class TodoList < ApplicationRecord 2 | has_many :todo_items 3 | end 4 | -------------------------------------------------------------------------------- /integration/todo/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Todo 5 | <%= csrf_meta_tags %> 6 | 7 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 8 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 9 | 10 | 11 | 12 | <%= yield %> 13 | 14 | 15 | -------------------------------------------------------------------------------- /integration/todo/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /integration/todo/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /integration/todo/app/views/todo_items/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for([@todo_list, @todo_list.todo_items.build]) do |f| %> 2 | <%= f.text_field :content, placeholder: "New Todo" %> 3 | <%= f.submit %> 4 | <% end %> -------------------------------------------------------------------------------- /integration/todo/app/views/todo_items/_todo_item.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if todo_item.completed? %> 3 |
4 | <%= link_to "Mark as Complete", complete_todo_list_todo_item_path(@todo_list, todo_item.id), method: :patch %> 5 |
6 |
7 |

<%= todo_item.content %>

8 |
9 |
10 | <%= link_to "Delete", todo_list_todo_item_path(@todo_list, todo_item.id), method: :delete, data: { confirm: "Are you sure?" } %> 11 |
12 | <% else %> 13 |
14 | <%= link_to "Mark as Complete", complete_todo_list_todo_item_path(@todo_list, todo_item.id), method: :patch %> 15 |
16 |
17 |

<%= todo_item.content %>

18 |
19 |
20 | <%= link_to "Delete", todo_list_todo_item_path(@todo_list, todo_item.id), method: :delete, data: { confirm: "Are you sure?" } %> 21 |
22 | <% end %> 23 |
-------------------------------------------------------------------------------- /integration/todo/app/views/todo_lists/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: todo_list, local: true) do |form| %> 2 | <% if todo_list.errors.any? %> 3 |
4 |

<%= pluralize(todo_list.errors.count, "error") %> prohibited this todo_list from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= form.label :title %> 16 | <%= form.text_field :title, id: :todo_list_title %> 17 |
18 | 19 |
20 | <%= form.label :description %> 21 | <%= form.text_area :description, id: :todo_list_description %> 22 |
23 | 24 |
25 | <%= form.submit %> 26 |
27 | <% end %> 28 | -------------------------------------------------------------------------------- /integration/todo/app/views/todo_lists/_todo_list.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! todo_list, :id, :title, :description, :created_at, :updated_at 2 | json.url todo_list_url(todo_list, format: :json) 3 | -------------------------------------------------------------------------------- /integration/todo/app/views/todo_lists/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing Todo List

2 | 3 | <%= render 'form', todo_list: @todo_list %> 4 | 5 | <%= link_to 'Show', @todo_list %> | 6 | <%= link_to 'Back', todo_lists_path %> 7 | -------------------------------------------------------------------------------- /integration/todo/app/views/todo_lists/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

Todo Lists

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% @todo_lists.each do |todo_list| %> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <% end %> 24 | 25 |
TitleDescription
<%= todo_list.title %><%= todo_list.description %><%= link_to 'Show', todo_list %><%= link_to 'Edit', edit_todo_list_path(todo_list) %><%= link_to 'Destroy', todo_list, method: :delete, data: { confirm: 'Are you sure?' } %>
26 | 27 |
28 | 29 | <%= link_to 'New Todo List', new_todo_list_path %> 30 | -------------------------------------------------------------------------------- /integration/todo/app/views/todo_lists/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array! @todo_lists, partial: 'todo_lists/todo_list', as: :todo_list 2 | -------------------------------------------------------------------------------- /integration/todo/app/views/todo_lists/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Todo List

2 | 3 | <%= render 'form', todo_list: @todo_list %> 4 | 5 | <%= link_to 'Back', todo_lists_path %> 6 | -------------------------------------------------------------------------------- /integration/todo/app/views/todo_lists/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | Title: 5 | <%= @todo_list.title %> 6 |

7 | 8 |

9 | Description: 10 | <%= @todo_list.description %> 11 |

12 | 13 |
14 | <%= render @todo_list.todo_items %> 15 |
16 | <%= render "todo_items/form" %> 17 |
18 |
19 | 20 | <%= link_to 'Edit', edit_todo_list_path(@todo_list) %> | 21 | <%= link_to 'Back', todo_lists_path %> 22 | -------------------------------------------------------------------------------- /integration/todo/app/views/todo_lists/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! "todo_lists/todo_list", todo_list: @todo_list 2 | -------------------------------------------------------------------------------- /integration/todo/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /integration/todo/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /integration/todo/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /integration/todo/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # Install JavaScript dependencies if using Yarn 22 | # system('bin/yarn') 23 | 24 | 25 | # puts "\n== Copying sample files ==" 26 | # unless File.exist?('config/database.yml') 27 | # cp 'config/database.yml.sample', 'config/database.yml' 28 | # end 29 | 30 | puts "\n== Preparing database ==" 31 | system! 'bin/rails db:setup' 32 | 33 | puts "\n== Removing old logs and tempfiles ==" 34 | system! 'bin/rails log:clear tmp:clear' 35 | 36 | puts "\n== Restarting application server ==" 37 | system! 'bin/rails restart' 38 | end 39 | -------------------------------------------------------------------------------- /integration/todo/bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /integration/todo/bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /integration/todo/bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | VENDOR_PATH = File.expand_path('..', __dir__) 3 | Dir.chdir(VENDOR_PATH) do 4 | begin 5 | exec "yarnpkg #{ARGV.join(" ")}" 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /integration/todo/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /integration/todo/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Todo 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 5.1 13 | 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration should go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded. 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /integration/todo/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /integration/todo/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | channel_prefix: todo_production 11 | -------------------------------------------------------------------------------- /integration/todo/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /integration/todo/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /integration/todo/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | # Print deprecation notices to the Rails logger. 35 | config.active_support.deprecation = :log 36 | 37 | # Raise an error on page load if there are pending migrations. 38 | config.active_record.migration_error = :page_load 39 | 40 | # Debug mode disables concatenation and preprocessing of assets. 41 | # This option may cause significant delays in view rendering with a large 42 | # number of complex assets. 43 | config.assets.debug = true 44 | 45 | # Suppress logger output for asset requests. 46 | config.assets.quiet = true 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | 51 | # Use an evented file watcher to asynchronously detect changes in source code, 52 | # routes, locales, etc. This feature depends on the listen gem. 53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 54 | end 55 | -------------------------------------------------------------------------------- /integration/todo/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`. 18 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or 19 | # `config/secrets.yml.key`. 20 | config.read_encrypted_secrets = true 21 | 22 | # Disable serving static files from the `/public` folder by default since 23 | # Apache or NGINX already handles this. 24 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 25 | 26 | # Compress JavaScripts and CSS. 27 | config.assets.js_compressor = :uglifier 28 | # config.assets.css_compressor = :sass 29 | 30 | # Do not fallback to assets pipeline if a precompiled asset is missed. 31 | config.assets.compile = false 32 | 33 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 34 | 35 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 36 | # config.action_controller.asset_host = 'http://assets.example.com' 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 41 | 42 | # Mount Action Cable outside main process or domain 43 | # config.action_cable.mount_path = nil 44 | # config.action_cable.url = 'wss://example.com/cable' 45 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 46 | 47 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 48 | # config.force_ssl = true 49 | 50 | # Use the lowest log level to ensure availability of diagnostic information 51 | # when problems arise. 52 | config.log_level = :debug 53 | 54 | # Prepend all log lines with the following tags. 55 | config.log_tags = [ :request_id ] 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Use a real queuing backend for Active Job (and separate queues per environment) 61 | # config.active_job.queue_adapter = :resque 62 | # config.active_job.queue_name_prefix = "todo_#{Rails.env}" 63 | config.action_mailer.perform_caching = false 64 | 65 | # Ignore bad email addresses and do not raise email delivery errors. 66 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 67 | # config.action_mailer.raise_delivery_errors = false 68 | 69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 70 | # the I18n.default_locale when a translation cannot be found). 71 | config.i18n.fallbacks = true 72 | 73 | # Send deprecation notices to registered listeners. 74 | config.active_support.deprecation = :notify 75 | 76 | # Use default logging formatter so that PID and timestamp are not suppressed. 77 | config.log_formatter = ::Logger::Formatter.new 78 | 79 | # Use a different logger for distributed setups. 80 | # require 'syslog/logger' 81 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 82 | 83 | if ENV["RAILS_LOG_TO_STDOUT"].present? 84 | logger = ActiveSupport::Logger.new(STDOUT) 85 | logger.formatter = config.log_formatter 86 | config.logger = ActiveSupport::TaggedLogging.new(logger) 87 | end 88 | 89 | # Do not dump schema after migrations. 90 | config.active_record.dump_schema_after_migration = false 91 | end 92 | -------------------------------------------------------------------------------- /integration/todo/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /integration/todo/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /integration/todo/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /integration/todo/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /integration/todo/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /integration/todo/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /integration/todo/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /integration/todo/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /integration/todo/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /integration/todo/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /integration/todo/config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # If you are preloading your application and using Active Record, it's 36 | # recommended that you close any connections to the database before workers 37 | # are forked to prevent connection leakage. 38 | # 39 | # before_fork do 40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) 41 | # end 42 | 43 | # The code in the `on_worker_boot` will be called if you are using 44 | # clustered mode by specifying a number of `workers`. After each worker 45 | # process is booted, this block will be run. If you are using the `preload_app!` 46 | # option, you will want to use this block to reconnect to any threads 47 | # or connections that may have been created at application boot, as Ruby 48 | # cannot share connections between processes. 49 | # 50 | # on_worker_boot do 51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 52 | # end 53 | # 54 | 55 | # Allow puma to be restarted by `rails restart` command. 56 | plugin :tmp_restart 57 | -------------------------------------------------------------------------------- /integration/todo/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :todo_lists do 3 | resources :todo_items do 4 | member do 5 | patch :complete 6 | end 7 | end 8 | end 9 | 10 | root "todo_lists#index" 11 | end 12 | -------------------------------------------------------------------------------- /integration/todo/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | # Shared secrets are available across all environments. 14 | 15 | # shared: 16 | # api_key: a1B2c3D4e5F6 17 | 18 | # Environmental secrets are only available for that specific environment. 19 | 20 | development: 21 | secret_key_base: 93e0f81d8e9dcf955f7480867ceb5a969f09837e83a61d85de4ef4c32b3a84c87516dcde79e4c9442f75c0d7c2b6fc259f34e90118fe456570ee94acc4791881 22 | 23 | test: 24 | secret_key_base: fa4aa30ead1cbd541294dc4a492f4c409fa2d52e2f1937634a817dcbc68f4bf3559cfe80a0344d27f99d047d368f309fb8f092ec91b692cf49ee61338c216b3c 25 | 26 | # Do not keep production secrets in the unencrypted secrets file. 27 | # Instead, either read values from the environment. 28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets 29 | # and move the `production:` environment over there. 30 | 31 | production: 32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 33 | -------------------------------------------------------------------------------- /integration/todo/config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /integration/todo/db/migrate/20180818210306_create_todo_lists.rb: -------------------------------------------------------------------------------- 1 | class CreateTodoLists < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :todo_lists do |t| 4 | t.string :title 5 | t.text :description 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /integration/todo/db/migrate/20180819121311_create_todo_items.rb: -------------------------------------------------------------------------------- 1 | class CreateTodoItems < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :todo_items do |t| 4 | t.string :content 5 | t.references :todo_list, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /integration/todo/db/migrate/20180819123754_add_completed_at_to_todo_items.rb: -------------------------------------------------------------------------------- 1 | class AddCompletedAtToTodoItems < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :todo_items, :completed_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /integration/todo/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20180819123754) do 14 | 15 | create_table "todo_items", force: :cascade do |t| 16 | t.string "content" 17 | t.integer "todo_list_id" 18 | t.datetime "created_at", null: false 19 | t.datetime "updated_at", null: false 20 | t.datetime "completed_at" 21 | t.index ["todo_list_id"], name: "index_todo_items_on_todo_list_id" 22 | end 23 | 24 | create_table "todo_lists", force: :cascade do |t| 25 | t.string "title" 26 | t.text "description" 27 | t.datetime "created_at", null: false 28 | t.datetime "updated_at", null: false 29 | end 30 | 31 | end 32 | -------------------------------------------------------------------------------- /integration/todo/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /integration/todo/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/lib/assets/.keep -------------------------------------------------------------------------------- /integration/todo/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/lib/tasks/.keep -------------------------------------------------------------------------------- /integration/todo/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/log/.keep -------------------------------------------------------------------------------- /integration/todo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo", 3 | "private": true, 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /integration/todo/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /integration/todo/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /integration/todo/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /integration/todo/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /integration/todo/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/public/apple-touch-icon.png -------------------------------------------------------------------------------- /integration/todo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/public/favicon.ico -------------------------------------------------------------------------------- /integration/todo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /integration/todo/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=fortititudetec:todo 2 | sonar.projectName=ToDo Application 3 | sonar.projectVersion=1.0 4 | sonar.sourceEncoding=UTF-8 5 | sonar.sources=app 6 | sonar.exclusions=**/node_modules/**,**/*.spec.ts 7 | sonar.tests=test 8 | sonar.test.inclusions=**/*.spec.ts 9 | 10 | sonar.ruby.file.suffixes=rb,ruby 11 | sonar.ruby.coverage.reportPath=coverage/.resultset.json 12 | sonar.ruby.coverage.framework=MiniTest 13 | sonar.ruby.rubocopConfig=.rubocop.yml 14 | sonar.ruby.rubocop=/usr/bin/rubocop 15 | sonar.ruby.rubocop.reportPath=rubocop-result.json 16 | sonar.ruby.rubocop.filePath=. 17 | -------------------------------------------------------------------------------- /integration/todo/test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /integration/todo/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/test/controllers/.keep -------------------------------------------------------------------------------- /integration/todo/test/controllers/todo_items_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TodoItemsControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /integration/todo/test/controllers/todo_lists_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TodoListsControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @todo_list = todo_lists(:one) 6 | end 7 | 8 | test "should get index" do 9 | get todo_lists_url 10 | assert_response :success 11 | end 12 | 13 | test "should get new" do 14 | get new_todo_list_url 15 | assert_response :success 16 | end 17 | 18 | test "should create todo_list" do 19 | assert_difference('TodoList.count') do 20 | post todo_lists_url, params: { todo_list: { description: @todo_list.description, title: @todo_list.title } } 21 | end 22 | 23 | assert_redirected_to todo_list_url(TodoList.last) 24 | end 25 | 26 | test "should show todo_list" do 27 | get todo_list_url(@todo_list) 28 | assert_response :success 29 | end 30 | 31 | test "should get edit" do 32 | get edit_todo_list_url(@todo_list) 33 | assert_response :success 34 | end 35 | 36 | test "should update todo_list" do 37 | patch todo_list_url(@todo_list), params: { todo_list: { description: @todo_list.description, title: @todo_list.title } } 38 | assert_redirected_to todo_list_url(@todo_list) 39 | end 40 | 41 | test "should destroy todo_list" do 42 | assert_difference('TodoList.count', -1) do 43 | delete todo_list_url(@todo_list) 44 | end 45 | 46 | assert_redirected_to todo_lists_url 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /integration/todo/test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/test/fixtures/.keep -------------------------------------------------------------------------------- /integration/todo/test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/test/fixtures/files/.keep -------------------------------------------------------------------------------- /integration/todo/test/fixtures/todo_items.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | content: MyString 5 | todo_list: one 6 | 7 | two: 8 | content: MyString 9 | todo_list: two 10 | -------------------------------------------------------------------------------- /integration/todo/test/fixtures/todo_lists.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | title: MyString 5 | description: MyText 6 | 7 | two: 8 | title: MyString 9 | description: MyText 10 | -------------------------------------------------------------------------------- /integration/todo/test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/test/helpers/.keep -------------------------------------------------------------------------------- /integration/todo/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/test/integration/.keep -------------------------------------------------------------------------------- /integration/todo/test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/test/mailers/.keep -------------------------------------------------------------------------------- /integration/todo/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/test/models/.keep -------------------------------------------------------------------------------- /integration/todo/test/models/todo_item_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TodoItemTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /integration/todo/test/models/todo_list_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TodoListTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /integration/todo/test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/test/system/.keep -------------------------------------------------------------------------------- /integration/todo/test/system/todo_lists_test.rb: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class TodoListsTest < ApplicationSystemTestCase 4 | # test "visiting the index" do 5 | # visit todo_lists_url 6 | # 7 | # assert_selector "h1", text: "TodoList" 8 | # end 9 | end 10 | -------------------------------------------------------------------------------- /integration/todo/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | 4 | require File.expand_path('../../config/environment', __FILE__) 5 | require 'rails/test_help' 6 | 7 | Minitest::Reporters.use! 8 | 9 | class ActiveSupport::TestCase 10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 11 | fixtures :all 12 | 13 | # Add more helper methods to be used by all tests here... 14 | end 15 | -------------------------------------------------------------------------------- /integration/todo/tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/tmp/.keep -------------------------------------------------------------------------------- /integration/todo/vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fortitudetec/sonar-ruby-plugin/3d82c28e5e46fe2a5b9cb6149877b79a3a969a8f/integration/todo/vendor/.keep -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.fortitudetec.sonar 8 | sonar-ruby-plugin 9 | 1.1.0 10 | sonar-plugin 11 | 12 | Sonar Ruby Plugin 13 | Analyze Ruby Source for code quality and code test coverage 14 | 2017 15 | https://github.com/fortitudetec/sonar-ruby-plugin 16 | 17 | 18 | Fortitude Technologies 19 | https://fortitudetec.com 20 | 21 | 22 | 23 | 24 | chrisrohr 25 | Chris Rohr 26 | 27 | 28 | 29 | 30 | 31 | MIT License 32 | http://www.opensource.org/licenses/mit-license.php 33 | 34 | 35 | 36 | 37 | https://@github.com/fortitudetec/sonar-ruby-plugin 38 | scm:git:git@github.com:fortitudetec/sonar-ruby-plugin.git 39 | 40 | 41 | 42 | Github 43 | https://github.com/fortitudetec/sonar-ruby-plugin/issues 44 | 45 | 46 | 47 | Travis 48 | https://travis-ci.org/fortitudetec/sonar-ruby-plugin 49 | 50 | 51 | 52 | UTF-8 53 | 54 | 5.6 55 | 1.8 56 | 1.21 57 | 58 | target/surefire-reports 59 | target/jacoco.exec 60 | 61 | 62 | 63 | 64 | org.sonarsource.sonarqube 65 | sonar-plugin-api 66 | ${sonar.buildVersion} 67 | provided 68 | 69 | 70 | 71 | org.sonarsource.sslr-squid-bridge 72 | sslr-squid-bridge 73 | 2.6.1 74 | 75 | 76 | 77 | org.yaml 78 | snakeyaml 79 | 1.17 80 | 81 | 82 | 83 | org.slf4j 84 | slf4j-api 85 | 1.7.24 86 | 87 | 88 | 89 | org.apache.commons 90 | commons-lang3 91 | 3.5 92 | 93 | 94 | 95 | com.google.guava 96 | guava 97 | 21.0 98 | 99 | 100 | 101 | com.fasterxml.jackson.core 102 | jackson-core 103 | 2.8.7 104 | 105 | 106 | 107 | com.fasterxml.jackson.core 108 | jackson-annotations 109 | 2.8.0 110 | 111 | 112 | 113 | com.fasterxml.jackson.core 114 | jackson-databind 115 | 2.8.7 116 | 117 | 118 | 119 | org.projectlombok 120 | lombok 121 | 1.16.6 122 | 123 | 124 | 125 | junit 126 | junit 127 | 4.12 128 | test 129 | 130 | 131 | org.assertj 132 | assertj-core 133 | 3.8.0 134 | test 135 | 136 | 137 | org.mockito 138 | mockito-core 139 | 1.10.19 140 | test 141 | 142 | 143 | 144 | 145 | 146 | 147 | org.sonarsource.sonar-packaging-maven-plugin 148 | sonar-packaging-maven-plugin 149 | 1.16 150 | true 151 | 152 | ruby 153 | com.fortitudetec.sonar.plugins.ruby.RubyPlugin 154 | Ruby 155 | 156 | 157 | 158 | org.apache.maven.plugins 159 | maven-compiler-plugin 160 | 2.5.1 161 | 162 | ${jdk.min.version} 163 | ${jdk.min.version} 164 | 165 | true 166 | 167 | 168 | 169 | 170 | maven-shade-plugin 171 | 172 | 173 | package 174 | 175 | shade 176 | 177 | 178 | false 179 | true 180 | false 181 | 182 | 183 | cglib:cglib-nodep 184 | 185 | ** 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | org.sonarsource.scanner.maven 196 | sonar-maven-plugin 197 | 3.2 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey = sonarqube-ruby-plugin 2 | sonar.sources = . 3 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/PathResolver.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.sonar.api.batch.BatchSide; 6 | import org.sonar.api.batch.sensor.SensorContext; 7 | 8 | import java.io.File; 9 | 10 | @BatchSide 11 | public class PathResolver { 12 | private static final Logger LOG = LoggerFactory.getLogger(PathResolver.class); 13 | 14 | public String getPath(SensorContext context, String settingKey, String defaultValue) { 15 | // Prefer the specified path 16 | String toReturn = context.settings().getString(settingKey); 17 | 18 | // Fall back to a file system search if null or doesn't exist 19 | if (toReturn == null || toReturn.isEmpty()) { 20 | LOG.debug("Path {} not specified, falling back to {}", settingKey, defaultValue); 21 | toReturn = defaultValue; 22 | } 23 | else { 24 | LOG.debug("Found {} path to be '{}'", settingKey, toReturn); 25 | } 26 | 27 | return getAbsolutePath(context, toReturn); 28 | } 29 | 30 | private String getAbsolutePath(SensorContext context, String toReturn) { 31 | if (toReturn != null) { 32 | File candidateFile = new java.io.File(toReturn); 33 | if (!candidateFile.isAbsolute()) { 34 | candidateFile = new java.io.File(context.fileSystem().baseDir().getAbsolutePath(), toReturn); 35 | } 36 | 37 | if (!doesFileExist(candidateFile)) { 38 | return null; 39 | } 40 | 41 | return candidateFile.getAbsolutePath(); 42 | } 43 | 44 | return null; 45 | } 46 | 47 | private boolean doesFileExist(File f) { 48 | return f.exists(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/Ruby.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.sonar.api.config.Settings; 6 | import org.sonar.api.resources.AbstractLanguage; 7 | 8 | import java.util.List; 9 | 10 | public class Ruby extends AbstractLanguage { 11 | 12 | public static final String LANGUAGE_KEY = "rb"; 13 | 14 | private static final String[] DEFAULT_FILE_SUFFIXES = { "rb", "Gemfile", "gemspec", "rake", "spec", "Capfile", "ru", "Rakefile" }; 15 | 16 | public static final String[] RUBY_KEYWORDS_ARRAY = 17 | { 18 | "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", 19 | "do", "else", "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", 20 | "next", "nil", "not", "or", "redo", "rescue", "retry", "return", "self", "super", 21 | "then", "true", "undef", "unless", "until", "when", "while", "yield" 22 | }; 23 | 24 | private Settings settings; 25 | 26 | public Ruby(Settings settings) { 27 | super(LANGUAGE_KEY, "Ruby"); 28 | this.settings = settings; 29 | } 30 | 31 | @Override 32 | public String[] getFileSuffixes() { 33 | String[] suffixes = filterEmptyStrings(settings.getStringArray("sonar.ruby.file.suffixes")); 34 | return suffixes.length == 0 ? Ruby.DEFAULT_FILE_SUFFIXES : suffixes; 35 | } 36 | 37 | private static String[] filterEmptyStrings(String[] stringArray) { 38 | List nonEmptyStrings = Lists.newArrayList(); 39 | for (String string : stringArray) { 40 | if (StringUtils.isNotBlank(string.trim())) { 41 | nonEmptyStrings.add(string.trim()); 42 | } 43 | } 44 | return nonEmptyStrings.toArray(new String[0]); 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/RubyPlugin.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | import com.fortitudetec.sonar.plugins.ruby.metrics.CombinedCoverageSensor; 4 | import com.fortitudetec.sonar.plugins.ruby.metrics.RubyMetricsSensor; 5 | import com.fortitudetec.sonar.plugins.ruby.rubocop.RubocopExecutor; 6 | import com.fortitudetec.sonar.plugins.ruby.rubocop.RubocopParser; 7 | import com.fortitudetec.sonar.plugins.ruby.rubocop.RubocopSensor; 8 | import com.fortitudetec.sonar.plugins.ruby.simplecov.SimpleCovParser; 9 | import com.fortitudetec.sonar.plugins.ruby.simplecov.SimpleCovSensor; 10 | import org.sonar.api.Plugin; 11 | import org.sonar.api.config.PropertyDefinition; 12 | import org.sonar.api.resources.Qualifiers; 13 | 14 | public class RubyPlugin implements Plugin { 15 | 16 | private static final String RUBY_CATEGORY = "Ruby"; 17 | 18 | // Subcategories 19 | private static final String GENERAL = "General"; 20 | private static final String TEST_AND_COVERAGE = "Tests and Coverage"; 21 | private static final String RUBOCOP = "Rubocop"; 22 | 23 | // Properties 24 | public static final String FILE_SUFFIXES = "sonar.ruby.file.suffixes"; 25 | public static final String SIMPLECOV_REPORT_PATH = "sonar.ruby.coverage.reportPath"; 26 | public static final String TEST_FRAMEWORK = "sonar.ruby.coverage.framework"; 27 | public static final String RUBOCOP_CONFIG = "sonar.ruby.rubocopConfig"; 28 | public static final String RUBOCOP_BIN = "sonar.ruby.rubocop"; 29 | public static final String RUBOCOP_REPORT_PATH = "sonar.ruby.rubocop.reportPath"; 30 | public static final String RUBOCOP_FILES_PATH = "sonar.ruby.rubocop.filePath"; 31 | 32 | public void define(Context context) { 33 | 34 | context.addExtensions( 35 | PropertyDefinition.builder(FILE_SUFFIXES) 36 | .name("File Suffixes") 37 | .description("Comma separated list of Ruby files to analyze.") 38 | .category(RUBY_CATEGORY) 39 | .subCategory(GENERAL) 40 | .onQualifiers(Qualifiers.PROJECT) 41 | .defaultValue("rb") 42 | .build(), 43 | 44 | // SimpleCov 45 | PropertyDefinition.builder(SIMPLECOV_REPORT_PATH) 46 | .name("Path to coverage reports") 47 | .description("Path to coverage reports.") 48 | .category(RUBY_CATEGORY) 49 | .subCategory(TEST_AND_COVERAGE) 50 | .onQualifiers(Qualifiers.PROJECT) 51 | .defaultValue("coverage/.resultset.json") 52 | .build(), 53 | 54 | PropertyDefinition.builder(TEST_FRAMEWORK) 55 | .name("Name of test framework") 56 | .description("Name of test framework.") 57 | .category(RUBY_CATEGORY) 58 | .subCategory(TEST_AND_COVERAGE) 59 | .onQualifiers(Qualifiers.PROJECT) 60 | .defaultValue("RSpec") 61 | .build(), 62 | 63 | // RUBOCOP 64 | PropertyDefinition.builder(RUBOCOP_CONFIG) 65 | .name("Rubocop configuration") 66 | .description("Path to the Rubocop configuration file to use in rubocop analysis. Set to empty to use the default.") 67 | .category(RUBY_CATEGORY) 68 | .subCategory(RUBOCOP) 69 | .onQualifiers(Qualifiers.PROJECT) 70 | .defaultValue(".rubocop.yml") 71 | .build(), 72 | 73 | PropertyDefinition.builder(RUBOCOP_BIN) 74 | .name("Rubocop executable") 75 | .description("Path to the rubocop executable to use in rubocop analysis. Set to empty to use the default one.") 76 | .category(RUBY_CATEGORY) 77 | .subCategory(RUBOCOP) 78 | .onQualifiers(Qualifiers.PROJECT) 79 | .defaultValue("") 80 | .build(), 81 | 82 | PropertyDefinition.builder(RUBOCOP_FILES_PATH) 83 | .name("Rubocop files to check") 84 | .description("The path to the files for rubocop to check") 85 | .category(RUBY_CATEGORY) 86 | .subCategory(RUBOCOP) 87 | .onQualifiers(Qualifiers.PROJECT) 88 | .defaultValue(".") 89 | .build(), 90 | 91 | PropertyDefinition.builder(RUBOCOP_REPORT_PATH) 92 | .name("Rubocop's reports") 93 | .description("Path to Rubocop's report file, relative to projects root") 94 | .category(RUBY_CATEGORY) 95 | .subCategory(RUBOCOP) 96 | .onQualifiers(Qualifiers.PROJECT) 97 | .defaultValue("rubocop-result.json") 98 | .build(), 99 | 100 | RubyRuleProfile.class, 101 | Ruby.class, 102 | RubyRulesDefinition.class, 103 | RubocopSensor.class, 104 | 105 | PathResolver.class, 106 | RubocopExecutor.class, 107 | RubocopParser.class, 108 | RubyMetricsSensor.class, 109 | 110 | SimpleCovSensor.class, 111 | SimpleCovParser.class, 112 | CombinedCoverageSensor.class 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/RubyRuleProfile.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | import com.fortitudetec.sonar.plugins.ruby.model.RubocopRule; 4 | import org.sonar.api.profiles.ProfileDefinition; 5 | import org.sonar.api.profiles.RulesProfile; 6 | import org.sonar.api.rules.Rule; 7 | import org.sonar.api.utils.ValidationMessages; 8 | 9 | public class RubyRuleProfile extends ProfileDefinition { 10 | @Override 11 | public RulesProfile createProfile(ValidationMessages validationMessages) { 12 | RulesProfile profile = RulesProfile.create("Rubocop", Ruby.LANGUAGE_KEY); 13 | 14 | RubyRulesDefinition rules = new RubyRulesDefinition(); 15 | 16 | activateRule(profile, RubyRulesDefinition.RUBY_LINT_UNKNOWN_RULE.key); 17 | 18 | for (RubocopRule coreRule : rules.getCoreRules()) { 19 | activateRule(profile, coreRule.key); 20 | } 21 | 22 | return profile; 23 | } 24 | 25 | private static void activateRule(RulesProfile profile, String ruleKey) { 26 | profile.activateRule(Rule.create("rubocop", ruleKey), null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/RubyRulesDefinition.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | import static org.apache.commons.lang3.StringUtils.startsWith; 4 | import static org.sonar.api.rule.Severity.CRITICAL; 5 | import static org.sonar.api.rule.Severity.INFO; 6 | import static org.sonar.api.rule.Severity.MINOR; 7 | import static org.sonar.api.rules.RuleType.BUG; 8 | import static org.sonar.api.rules.RuleType.CODE_SMELL; 9 | import static org.sonar.api.rules.RuleType.VULNERABILITY; 10 | 11 | import com.fortitudetec.sonar.plugins.ruby.model.RubocopRule; 12 | import org.sonar.api.rule.RuleStatus; 13 | import org.sonar.api.rule.Severity; 14 | import org.sonar.api.rules.RuleType; 15 | import org.sonar.api.server.rule.RulesDefinition; 16 | import org.yaml.snakeyaml.Yaml; 17 | 18 | import java.io.InputStream; 19 | import java.util.ArrayList; 20 | import java.util.Comparator; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | public class RubyRulesDefinition implements RulesDefinition { 25 | 26 | /** The SonarQube rule that will contain all unknown TsLint issues. */ 27 | public static final RubocopRule RUBY_LINT_UNKNOWN_RULE = new RubocopRule( 28 | "rubocop-issue", 29 | Severity.MAJOR, 30 | "rubocop issues that are not yet known to the plugin", 31 | CODE_SMELL, 32 | "No description for Rubocop rule"); 33 | 34 | private List rubylintCoreRules = new ArrayList<>(); 35 | 36 | public RubyRulesDefinition() { 37 | loadCoreRules(); 38 | } 39 | 40 | @Override 41 | public void define(Context context) { 42 | NewRepository repository = 43 | context 44 | .createRepository("rubocop", Ruby.LANGUAGE_KEY) 45 | .setName("Rubocop Analyzer"); 46 | 47 | createRule(repository, RUBY_LINT_UNKNOWN_RULE); 48 | 49 | // add the Rubocop builtin core rules 50 | for (RubocopRule coreRule : rubylintCoreRules) { 51 | createRule(repository, coreRule); 52 | } 53 | 54 | repository.done(); 55 | } 56 | 57 | public List getCoreRules() { 58 | return rubylintCoreRules; 59 | } 60 | 61 | private void createRule(NewRepository repository, RubocopRule rubocopRule) { 62 | repository 63 | .createRule(rubocopRule.key) 64 | .setName(rubocopRule.name) 65 | .setSeverity(rubocopRule.severity) 66 | .setHtmlDescription(rubocopRule.htmlDescription) 67 | .setStatus(RuleStatus.READY) 68 | .setType(rubocopRule.debtType); 69 | } 70 | 71 | @SuppressWarnings("unchecked") 72 | private void loadCoreRules() { 73 | InputStream coreRulesStream = RubyRulesDefinition.class.getResourceAsStream("/rubocop/rubocop.yml"); 74 | Yaml yaml = new Yaml(); 75 | 76 | Map> rules = (Map>) yaml.load(coreRulesStream); 77 | 78 | rules.forEach((rule, metadata) -> { 79 | if ((Boolean) metadata.get("Enabled")) { 80 | rubylintCoreRules.add(new RubocopRule(rule, findSeverity(rule), rule, findType(rule), (String) metadata.get("Description"))); 81 | } 82 | }); 83 | 84 | rubylintCoreRules.sort(Comparator.comparing(r -> r.key)); 85 | } 86 | 87 | private static String findSeverity(String rule) { 88 | String severity = MINOR; 89 | 90 | if (startsWith(rule, "Metrics") || startsWith(rule, "Bundler")) { 91 | severity = INFO; 92 | } else if (startsWith(rule, "Security")) { 93 | severity = CRITICAL; 94 | } 95 | 96 | return severity; 97 | } 98 | 99 | private static RuleType findType(String rule) { 100 | RuleType type = CODE_SMELL; 101 | 102 | if (startsWith(rule, "Performance")) { 103 | type = BUG; 104 | } else if (startsWith(rule, "Security")) { 105 | type = VULNERABILITY; 106 | } 107 | 108 | return type; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/metrics/ClassCountParser.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.metrics; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import org.apache.commons.io.LineIterator; 5 | import org.apache.commons.lang.StringUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | 12 | public class ClassCountParser { 13 | private static final Logger LOG = LoggerFactory.getLogger(ClassCountParser.class); 14 | 15 | private ClassCountParser() {} 16 | 17 | public static int countClasses(File file) { 18 | int numClasses = 0; 19 | LineIterator iterator = null; 20 | try { 21 | iterator = FileUtils.lineIterator(file); 22 | 23 | while (iterator.hasNext()) { 24 | String line = iterator.nextLine(); 25 | if (StringUtils.contains(line.trim(), "class ")) { 26 | numClasses++; 27 | } 28 | } 29 | } catch (IOException e) { 30 | LOG.error("Error determining class count for file " + file, e); 31 | } finally { 32 | LineIterator.closeQuietly(iterator); 33 | } 34 | 35 | return numClasses; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/metrics/CombinedCoverageSensor.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.metrics; 2 | 3 | import com.fortitudetec.sonar.plugins.ruby.Ruby; 4 | import com.fortitudetec.sonar.plugins.ruby.simplecov.SimpleCovSensor; 5 | import org.sonar.api.batch.sensor.Sensor; 6 | import org.sonar.api.batch.sensor.SensorContext; 7 | import org.sonar.api.batch.sensor.SensorDescriptor; 8 | 9 | public class CombinedCoverageSensor implements Sensor { 10 | 11 | private RubyMetricsSensor locSensor; 12 | private SimpleCovSensor coverageSensor; 13 | 14 | public CombinedCoverageSensor(RubyMetricsSensor locSensor, SimpleCovSensor coverageSensor) { 15 | this.locSensor = locSensor; 16 | this.coverageSensor = coverageSensor; 17 | } 18 | 19 | @Override 20 | public void describe(SensorDescriptor descriptor) { 21 | descriptor.name("Combined SimpleCov and LOC sensor"); 22 | descriptor.onlyOnLanguage(Ruby.LANGUAGE_KEY); 23 | } 24 | 25 | @Override 26 | public void execute(SensorContext context) { 27 | locSensor.execute(context); 28 | coverageSensor.execute(context); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/metrics/CommentCountParser.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.metrics; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import org.apache.commons.io.LineIterator; 5 | import org.apache.commons.lang.StringUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | 12 | public class CommentCountParser { 13 | private static final Logger LOG = LoggerFactory.getLogger(CommentCountParser.class); 14 | 15 | private CommentCountParser() {} 16 | 17 | public static int countLinesOfComment(File file) { 18 | int numComments = 0; 19 | LineIterator iterator = null; 20 | try { 21 | iterator = FileUtils.lineIterator(file); 22 | 23 | while (iterator.hasNext()) { 24 | String line = iterator.nextLine(); 25 | if (StringUtils.startsWith(line.trim(), "#")) { 26 | numComments++; 27 | } 28 | } 29 | } catch (IOException e) { 30 | LOG.error("Error determining comment count for file " + file, e); 31 | } finally { 32 | LineIterator.closeQuietly(iterator); 33 | } 34 | 35 | return numComments; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/metrics/RubyMetricsSensor.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.metrics; 2 | 3 | import com.fortitudetec.sonar.plugins.ruby.Ruby; 4 | import com.fortitudetec.sonar.plugins.ruby.squid.RubyRecognizer; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.sonar.api.batch.BatchSide; 8 | import org.sonar.api.batch.fs.InputFile; 9 | import org.sonar.api.batch.sensor.SensorContext; 10 | import org.sonar.api.measures.CoreMetrics; 11 | import org.sonar.squidbridge.measures.Metric; 12 | import org.sonar.squidbridge.text.Source; 13 | 14 | import java.io.BufferedReader; 15 | import java.io.FileReader; 16 | import java.io.IOException; 17 | 18 | @BatchSide 19 | public class RubyMetricsSensor { 20 | private static final Logger LOG = LoggerFactory.getLogger(RubyMetricsSensor.class); 21 | 22 | public void execute(SensorContext ctx) { 23 | Iterable affectedFiles = gatherFiles(ctx); 24 | affectedFiles.forEach(inputFile -> setMetricsForFile(inputFile, ctx)); 25 | } 26 | 27 | private void setMetricsForFile(InputFile inputFile, SensorContext ctx) { 28 | try (FileReader fileReader = new FileReader(inputFile.file()); BufferedReader reader = new BufferedReader(fileReader)) { 29 | Source source = new Source(reader, new RubyRecognizer(), "#"); 30 | 31 | ctx.newMeasure().forMetric(CoreMetrics.NCLOC).on(inputFile).withValue(source.getMeasure(Metric.LINES_OF_CODE)).save(); 32 | ctx.newMeasure().forMetric(CoreMetrics.COMMENT_LINES).on(inputFile).withValue(CommentCountParser.countLinesOfComment(inputFile.file())).save(); 33 | ctx.newMeasure().forMetric(CoreMetrics.CLASSES).on(inputFile).withValue(ClassCountParser.countClasses(inputFile.file())).save(); 34 | 35 | } catch (IOException e) { 36 | LOG.warn("Unable to read ruby file to gather metrics.", e); 37 | } 38 | } 39 | 40 | private Iterable gatherFiles(SensorContext ctx) { 41 | return ctx 42 | .fileSystem() 43 | .inputFiles(ctx.fileSystem().predicates().hasLanguage(Ruby.LANGUAGE_KEY)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/model/RubocopIssue.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.model; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | @Getter 9 | @Setter 10 | @ToString 11 | @Builder 12 | public class RubocopIssue { 13 | private RubocopPosition position; 14 | private String failure; 15 | private String ruleName; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/model/RubocopPosition.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.model; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | @Getter 9 | @Setter 10 | @ToString 11 | @Builder 12 | public class RubocopPosition { 13 | private int line; 14 | private int character; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/model/RubocopRule.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.model; 2 | 3 | import org.sonar.api.rules.RuleType; 4 | 5 | public class RubocopRule { 6 | public final String key; 7 | public final String name; 8 | public final String severity; 9 | public final String htmlDescription; 10 | public final RuleType debtType; 11 | 12 | public RubocopRule(String key,String severity, String name, RuleType debtType, String htmlDescription) { 13 | this.key = key; 14 | this.severity = severity; 15 | this.name = name; 16 | this.debtType = debtType; 17 | this.htmlDescription = htmlDescription; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/rubocop/RubocopExecutor.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.rubocop; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | 5 | import com.google.common.base.Charsets; 6 | import com.google.common.io.Files; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.sonar.api.batch.BatchSide; 11 | import org.sonar.api.utils.System2; 12 | import org.sonar.api.utils.TempFolder; 13 | import org.sonar.api.utils.command.Command; 14 | import org.sonar.api.utils.command.CommandExecutor; 15 | import org.sonar.api.utils.command.StreamConsumer; 16 | import org.sonar.api.utils.command.StringStreamConsumer; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.util.List; 21 | 22 | @BatchSide 23 | public class RubocopExecutor { 24 | 25 | private static final Logger LOG = LoggerFactory.getLogger(RubocopExecutor.class); 26 | 27 | private boolean mustQuoteSpaceContainingPaths; 28 | private TempFolder tempFolder; 29 | 30 | public RubocopExecutor(System2 system, TempFolder tempFolder) { 31 | this.mustQuoteSpaceContainingPaths = system.isOsWindows(); 32 | this.tempFolder = tempFolder; 33 | } 34 | 35 | public String execute(RubocopExecutorConfig config, List files) { 36 | checkNotNull(config, "RubcopExecutorConfig must not be null"); 37 | checkNotNull(files, "List of files must not be null"); 38 | 39 | if (config.useExistingRubocopOutput()) { 40 | LOG.debug("Running with existing JSON file '{}' instead of calling rubocop", config.getPathToRubocopOutput()); 41 | return getFileContent(new File(config.getPathToRubocopOutput())); 42 | } 43 | 44 | File rubocopOutputFile = tempFolder.newFile(); 45 | String rubocopOutputFilePath = rubocopOutputFile.getAbsolutePath(); 46 | Command baseCommand = getBaseCommand(config, rubocopOutputFilePath); 47 | 48 | StringStreamConsumer stdOutConsumer = new StringStreamConsumer(); 49 | StringStreamConsumer stdErrConsumer = new StringStreamConsumer(); 50 | return getCommandOutput(baseCommand, stdOutConsumer, stdErrConsumer, rubocopOutputFile, config.getTimeoutMs()); 51 | } 52 | 53 | private String getFileContent(File rubocopOutputFile) { 54 | try { 55 | return Files.toString(rubocopOutputFile, Charsets.UTF_8); 56 | } catch (IOException e) { 57 | LOG.error("Failed to read Rubocop output", e); 58 | } 59 | 60 | return ""; 61 | } 62 | 63 | private Command getBaseCommand(RubocopExecutorConfig config, String tempPath) { 64 | Command command = 65 | Command 66 | .create(config.getPathToRubocop()) 67 | .addArgument(preparePath(config.getPathToRubocop())) 68 | .addArgument("--format") 69 | .addArgument("json"); 70 | 71 | if (StringUtils.isNotBlank(tempPath)) { 72 | command 73 | .addArgument("--out") 74 | .addArgument(preparePath(tempPath)); 75 | } 76 | 77 | command 78 | .addArgument("--config") 79 | .addArgument(preparePath(config.getConfigFile())); 80 | 81 | command.setNewShell(false); 82 | 83 | return command; 84 | } 85 | 86 | private String preparePath(String path) { 87 | if (path == null) { 88 | return ""; 89 | } else if (path.contains(" ") && mustQuoteSpaceContainingPaths) { 90 | return '"' + path + '"'; 91 | } else { 92 | return path; 93 | } 94 | } 95 | 96 | private String getCommandOutput(Command thisCommand, StreamConsumer stdOutConsumer, StreamConsumer stdErrConsumer, File rubocopOutputFile, Integer timeoutMs) { 97 | createExecutor().execute(thisCommand, stdOutConsumer, stdErrConsumer, timeoutMs); 98 | return getFileContent(rubocopOutputFile); 99 | } 100 | 101 | protected CommandExecutor createExecutor() { 102 | return CommandExecutor.create(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/rubocop/RubocopExecutorConfig.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.rubocop; 2 | 3 | import static org.apache.commons.lang3.StringUtils.isNotBlank; 4 | 5 | import com.fortitudetec.sonar.plugins.ruby.PathResolver; 6 | import com.fortitudetec.sonar.plugins.ruby.RubyPlugin; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | import org.sonar.api.batch.sensor.SensorContext; 10 | 11 | @Getter 12 | @Setter 13 | public class RubocopExecutorConfig { 14 | 15 | private String pathToRubocopOutput; 16 | private String pathToRubocop; 17 | private String pathToRubocopFiles; 18 | private String configFile; 19 | private Integer timeoutMs; 20 | 21 | public static RubocopExecutorConfig fromSettings(SensorContext ctx, PathResolver resolver) { 22 | RubocopExecutorConfig toReturn = new RubocopExecutorConfig(); 23 | 24 | toReturn.setPathToRubocop(resolver.getPath(ctx, RubyPlugin.RUBOCOP_BIN, "rubocop")); 25 | toReturn.setPathToRubocopFiles(resolver.getPath(ctx, RubyPlugin.RUBOCOP_FILES_PATH, ".")); 26 | toReturn.setConfigFile(resolver.getPath(ctx, RubyPlugin.RUBOCOP_CONFIG, "")); 27 | toReturn.setPathToRubocopOutput(resolver.getPath(ctx, RubyPlugin.RUBOCOP_REPORT_PATH, "rubocop-result.json")); 28 | 29 | toReturn.setTimeoutMs(6000); 30 | 31 | return toReturn; 32 | } 33 | 34 | public Boolean useExistingRubocopOutput() { 35 | return isNotBlank(pathToRubocopOutput); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/rubocop/RubocopParser.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.rubocop; 2 | 3 | import static java.util.stream.Collectors.toMap; 4 | 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fortitudetec.sonar.plugins.ruby.model.RubocopIssue; 7 | import com.fortitudetec.sonar.plugins.ruby.model.RubocopPosition; 8 | import com.fortitudetec.sonar.plugins.ruby.rubocop.model.RubocopFile; 9 | import com.fortitudetec.sonar.plugins.ruby.rubocop.model.RubocopOffense; 10 | import com.fortitudetec.sonar.plugins.ruby.rubocop.model.RubocopResult; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.sonar.api.batch.BatchSide; 14 | 15 | import java.io.IOException; 16 | import java.util.Collections; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.stream.Collectors; 20 | 21 | @BatchSide 22 | public class RubocopParser { 23 | private static final Logger LOG = LoggerFactory.getLogger(RubocopParser.class); 24 | private static final ObjectMapper MAPPER = new ObjectMapper(); 25 | 26 | public Map> parse(String toParse) { 27 | return collectIssues(toParse).entrySet().stream() 28 | .filter(entry -> !entry.getValue().isEmpty()) 29 | .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); 30 | } 31 | 32 | @SuppressWarnings("unchecked") 33 | private Map> collectIssues(String resultsFile) { 34 | try { 35 | RubocopResult results = MAPPER.readValue(resultsFile, RubocopResult.class); 36 | 37 | return results.getFiles().stream() 38 | .collect(toMap(RubocopFile::getPath, this::populateIssues)); 39 | } catch (IOException e) { 40 | LOG.warn("Unable to parse results file", e); 41 | } 42 | 43 | return Collections.emptyMap(); 44 | } 45 | 46 | @SuppressWarnings("unchecked") 47 | private List populateIssues(RubocopFile file) { 48 | return file.getOffenses().stream() 49 | .map(offense -> RubocopIssue.builder() 50 | .ruleName(offense.getCopName()) 51 | .failure(offense.getMessage()) 52 | .position(populatePosition(offense)) 53 | .build()) 54 | .collect(Collectors.toList()); 55 | } 56 | 57 | private RubocopPosition populatePosition(RubocopOffense offense) { 58 | return RubocopPosition.builder() 59 | .line(offense.getLocation().get("line").intValue()) 60 | .character(offense.getLocation().get("column").intValue()) 61 | .build(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/rubocop/RubocopSensor.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.rubocop; 2 | 3 | import static com.google.common.collect.Lists.newArrayList; 4 | 5 | import com.fortitudetec.sonar.plugins.ruby.PathResolver; 6 | import com.fortitudetec.sonar.plugins.ruby.Ruby; 7 | import com.fortitudetec.sonar.plugins.ruby.RubyRulesDefinition; 8 | import com.fortitudetec.sonar.plugins.ruby.model.RubocopIssue; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.sonar.api.batch.fs.FileSystem; 12 | import org.sonar.api.batch.fs.InputFile; 13 | import org.sonar.api.batch.rule.ActiveRule; 14 | import org.sonar.api.batch.sensor.Sensor; 15 | import org.sonar.api.batch.sensor.SensorContext; 16 | import org.sonar.api.batch.sensor.SensorDescriptor; 17 | import org.sonar.api.batch.sensor.issue.NewIssue; 18 | import org.sonar.api.batch.sensor.issue.NewIssueLocation; 19 | import org.sonar.api.rule.RuleKey; 20 | 21 | import java.io.File; 22 | import java.util.Collection; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Set; 26 | import java.util.stream.Collectors; 27 | import java.util.stream.StreamSupport; 28 | 29 | public class RubocopSensor implements Sensor { 30 | private static final Logger LOG = LoggerFactory.getLogger(RubocopSensor.class); 31 | 32 | private PathResolver resolver; 33 | private RubocopExecutor executor; 34 | private RubocopParser parser; 35 | 36 | public RubocopSensor(PathResolver resolver, RubocopExecutor executor, RubocopParser parser) { 37 | this.resolver = resolver; 38 | this.executor = executor; 39 | this.parser = parser; 40 | } 41 | 42 | @Override 43 | public void describe(SensorDescriptor desc) { 44 | desc 45 | .name("Linting sensor for Ruby files") 46 | .onlyOnLanguage(Ruby.LANGUAGE_KEY); 47 | } 48 | 49 | @Override 50 | public void execute(SensorContext sensorContext) { 51 | RubocopExecutorConfig config = RubocopExecutorConfig.fromSettings(sensorContext, resolver); 52 | 53 | if (!config.useExistingRubocopOutput() && config.getPathToRubocop() == null) { 54 | LOG.warn("Path to rubocop not defined or not found. Skipping rubocop analysis."); 55 | return; 56 | } 57 | 58 | String jsonResults = this.executor.execute(config, findFilesToLint(sensorContext, config)); 59 | 60 | Map> issues = parser.parse(jsonResults); 61 | 62 | if (issues.isEmpty()) { 63 | LOG.warn("Rubocop returned no result at all"); 64 | return; 65 | } 66 | 67 | Collection allRules = sensorContext.activeRules().findByRepository("rubocop"); 68 | Set ruleNames = allRules.stream().map(rule -> rule.ruleKey().rule()).collect(Collectors.toSet()); 69 | 70 | issues.entrySet().forEach(rubyFilesIssues -> generateSonarIssuesFromResults(rubyFilesIssues, sensorContext, ruleNames)); 71 | } 72 | 73 | private List findFilesToLint(SensorContext context, RubocopExecutorConfig config) { 74 | if (config.useExistingRubocopOutput()) { 75 | return newArrayList(); 76 | } 77 | 78 | Iterable inputFiles = context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguage(Ruby.LANGUAGE_KEY)); 79 | 80 | return StreamSupport.stream(inputFiles.spliterator(), false) 81 | .map(InputFile::absolutePath) 82 | .collect(Collectors.toList()); 83 | } 84 | 85 | private void generateSonarIssuesFromResults(Map.Entry> rubyFilesIssues, SensorContext sensorContext, Set ruleNames) { 86 | List batchIssues = rubyFilesIssues.getValue(); 87 | 88 | if (batchIssues.isEmpty()) { 89 | return; 90 | } 91 | 92 | String filePath = rubyFilesIssues.getKey(); 93 | InputFile inputFile = findMatchingFile(sensorContext.fileSystem(), filePath); 94 | if (inputFile == null) { 95 | LOG.warn("Rubocop reported issues against a file that isn't in the analysis set - will be ignored: {}", filePath); 96 | return; 97 | } 98 | 99 | if (LOG.isDebugEnabled()) { 100 | LOG.debug("Handling Rubocop output for '{}' reporting against '{}'", filePath, inputFile.absolutePath()); 101 | } 102 | 103 | batchIssues.forEach(issue -> saveNewIssue(issue, inputFile, ruleNames, sensorContext)); 104 | } 105 | 106 | private InputFile findMatchingFile(FileSystem fs, String filePath) { 107 | File matchingFile = fs.resolvePath(filePath); 108 | 109 | if (matchingFile != null) { 110 | try { 111 | return fs.inputFile(fs.predicates().is(matchingFile)); 112 | } 113 | catch (IllegalArgumentException e) { 114 | LOG.error("Failed to resolve " + filePath + " to a single path", e); 115 | } 116 | } 117 | return null; 118 | } 119 | 120 | private void saveNewIssue(RubocopIssue issue, InputFile inputFile, Set ruleNames, SensorContext sensorContext) { 121 | // Make sure the rule we're violating is one we recognise - if not, we'll 122 | // fall back to the generic 'rubocop-issue' rule 123 | String ruleName = issue.getRuleName(); 124 | if (!ruleNames.contains(ruleName)) { 125 | ruleName = RubyRulesDefinition.RUBY_LINT_UNKNOWN_RULE.key; 126 | } 127 | 128 | NewIssue newIssue = 129 | sensorContext 130 | .newIssue() 131 | .forRule(RuleKey.of("rubocop", ruleName)); 132 | 133 | NewIssueLocation newIssueLocation = 134 | newIssue 135 | .newLocation() 136 | .on(inputFile) 137 | .message(issue.getFailure()) 138 | .at(inputFile.selectLine(issue.getPosition().getLine())); 139 | 140 | newIssue.at(newIssueLocation); 141 | newIssue.save(); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/rubocop/model/RubocopFile.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.rubocop.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.util.List; 7 | 8 | @Getter 9 | @Setter 10 | public class RubocopFile { 11 | private String path; 12 | private List offenses; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/rubocop/model/RubocopOffense.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.rubocop.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.util.Map; 8 | 9 | @Getter 10 | @Setter 11 | public class RubocopOffense { 12 | private String severity; 13 | private String message; 14 | private Boolean corrected; 15 | private Map location; 16 | 17 | @JsonProperty("cop_name") 18 | private String copName; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/rubocop/model/RubocopResult.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.rubocop.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | @Getter 10 | @Setter 11 | public class RubocopResult { 12 | 13 | private Map metadata; 14 | private List files; 15 | private Map summary; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/simplecov/SimpleCovParser.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.simplecov; 2 | 3 | import static java.util.Objects.nonNull; 4 | import static java.util.Objects.isNull; 5 | 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fortitudetec.sonar.plugins.ruby.Ruby; 8 | import com.fortitudetec.sonar.plugins.ruby.RubyPlugin; 9 | import org.apache.commons.io.FileUtils; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.sonar.api.batch.BatchSide; 14 | import org.sonar.api.batch.fs.InputFile; 15 | import org.sonar.api.batch.sensor.SensorContext; 16 | import org.sonar.api.batch.sensor.coverage.CoverageType; 17 | import org.sonar.api.batch.sensor.coverage.NewCoverage; 18 | 19 | import java.io.BufferedReader; 20 | import java.io.File; 21 | import java.io.FileNotFoundException; 22 | import java.io.FileReader; 23 | import java.io.IOException; 24 | import java.util.HashSet; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Set; 28 | import java.util.stream.Collectors; 29 | import java.util.stream.IntStream; 30 | import java.util.stream.StreamSupport; 31 | 32 | @BatchSide 33 | public class SimpleCovParser { 34 | private static final Logger LOG = LoggerFactory.getLogger(SimpleCovParser.class); 35 | 36 | private static final ObjectMapper MAPPER = new ObjectMapper(); 37 | 38 | @SuppressWarnings("unchecked") 39 | public Map parse(SensorContext ctx, File resultFile) throws IOException { 40 | 41 | Map>>> results = parseResultsFile(resultFile); 42 | 43 | String testFramework = ctx.settings().getString(RubyPlugin.TEST_FRAMEWORK); 44 | 45 | if (isNull(testFramework)) { 46 | LOG.warn("Test framework is not set, unable to parse coverage metrics"); 47 | return null; 48 | } 49 | 50 | Map> coverageByFile = results.get(testFramework).get("coverage"); 51 | 52 | Iterable inputFiles = ctx.fileSystem().inputFiles(ctx.fileSystem().predicates().hasLanguage(Ruby.LANGUAGE_KEY)); 53 | 54 | return StreamSupport.stream(inputFiles.spliterator(), false) 55 | .collect(Collectors.toMap(InputFile::absolutePath, 56 | file -> buildCoverageForFile(ctx, file, coverageByFile.get(file.absolutePath())))); 57 | } 58 | 59 | private NewCoverage buildCoverageForFile(SensorContext ctx, InputFile file, List lineCounts) { 60 | NewCoverage coverage = ctx.newCoverage() 61 | .onFile(file) 62 | .ofType(CoverageType.UNIT); 63 | 64 | if (lineCounts == null || lineCounts.isEmpty()) { 65 | updateForZeroCoverage(file, coverage, gatherNonCommentLinesOfCodeForFile(file)); 66 | } else { 67 | IntStream.range(0, lineCounts.size()) 68 | .forEach(idx -> coverageForLine(coverage, idx, lineCounts.get(idx))); 69 | } 70 | 71 | return coverage; 72 | } 73 | 74 | private void coverageForLine(NewCoverage coverage, int lineNumber, Integer lineCount) { 75 | if (nonNull(lineCount)) { 76 | coverage.lineHits(lineNumber + 1, lineCount); 77 | } 78 | } 79 | 80 | private Map parseResultsFile(File resultFile) throws IOException { 81 | String fileString = FileUtils.readFileToString(resultFile, "UTF-8"); 82 | return MAPPER.readValue(fileString, Map.class); 83 | } 84 | 85 | private Set gatherNonCommentLinesOfCodeForFile(InputFile inputFile) { 86 | HashSet toReturn = new HashSet<>(); 87 | 88 | int lineNumber = 0; 89 | 90 | try (FileReader fileReader = new FileReader(inputFile.file()); BufferedReader reader = new BufferedReader(fileReader)) { 91 | String line; 92 | while (nonNull(line = reader.readLine())) { 93 | lineNumber++; 94 | line = line.trim().replaceAll("\\n|\\t|\\s", ""); 95 | if (!(StringUtils.isBlank(line) || line.startsWith("#"))) { 96 | toReturn.add(lineNumber); 97 | } 98 | } 99 | 100 | reader.close(); 101 | 102 | } catch (FileNotFoundException e) { 103 | LOG.error("File not found", e); 104 | } catch (IOException e) { 105 | LOG.error("Error while reading BufferedReader", e); 106 | } 107 | 108 | return toReturn; 109 | } 110 | 111 | private void updateForZeroCoverage(InputFile inputFile, NewCoverage newCoverage, Set nonCommentLineNumbers) { 112 | if (nonCommentLineNumbers != null) { 113 | nonCommentLineNumbers.forEach(lineNumber -> newCoverage.lineHits(lineNumber, 0)); 114 | } 115 | else { 116 | IntStream.rangeClosed(0, inputFile.lines()) 117 | .forEach(idx -> newCoverage.lineHits(idx, 0)); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/simplecov/SimpleCovSensor.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.simplecov; 2 | 3 | import static java.util.Objects.isNull; 4 | 5 | import com.fortitudetec.sonar.plugins.ruby.RubyPlugin; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.sonar.api.batch.BatchSide; 9 | import org.sonar.api.batch.sensor.SensorContext; 10 | import org.sonar.api.batch.sensor.coverage.NewCoverage; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.util.Map; 15 | 16 | @BatchSide 17 | public class SimpleCovSensor { 18 | private static final Logger LOG = LoggerFactory.getLogger(SimpleCovSensor.class); 19 | 20 | public void execute(SensorContext context) { 21 | String reportPath = context.settings().getString(RubyPlugin.SIMPLECOV_REPORT_PATH); 22 | 23 | if (isNull(reportPath)) { 24 | LOG.warn("Report path is not set, unable to generate coverage metrics"); 25 | return; 26 | } 27 | 28 | SimpleCovParser parser = new SimpleCovParser(); 29 | 30 | try { 31 | Map fileCoverages = parser.parse(context, new File(reportPath)); 32 | fileCoverages.values().forEach(NewCoverage::save); 33 | } catch (IOException e) { 34 | LOG.warn("Unable to generate coverage metrics", e); 35 | } 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/squid/RubyFootPrint.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.squid; 2 | 3 | import com.fortitudetec.sonar.plugins.ruby.Ruby; 4 | import org.sonar.squidbridge.recognizer.Detector; 5 | import org.sonar.squidbridge.recognizer.EndWithDetector; 6 | import org.sonar.squidbridge.recognizer.KeywordsDetector; 7 | import org.sonar.squidbridge.recognizer.LanguageFootprint; 8 | 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | public class RubyFootPrint implements LanguageFootprint { 13 | private static final double END_WITH_DETECTOR = 0.95; 14 | private static final double KEYWORDS_DETECTOR = 0.3; 15 | 16 | private final Set detectors = new HashSet<>(); 17 | 18 | RubyFootPrint() { 19 | detectors.add(new EndWithDetector(END_WITH_DETECTOR, ')', '"', '\'')); 20 | detectors.add(new KeywordsDetector(KEYWORDS_DETECTOR, Ruby.RUBY_KEYWORDS_ARRAY)); 21 | } 22 | 23 | @Override 24 | public Set getDetectors() { 25 | return detectors; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/fortitudetec/sonar/plugins/ruby/squid/RubyRecognizer.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.squid; 2 | 3 | import org.sonar.squidbridge.recognizer.CodeRecognizer; 4 | 5 | public class RubyRecognizer extends CodeRecognizer { 6 | private static final double MAX_RECOGNIZE_PERCENT = 0.95; 7 | 8 | public RubyRecognizer() { 9 | super(MAX_RECOGNIZE_PERCENT, new RubyFootPrint()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/PathResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 8 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 9 | 10 | import java.io.File; 11 | import java.net.URL; 12 | 13 | public class PathResolverTest { 14 | private PathResolver resolver; 15 | private SensorContextTester sensorContext; 16 | 17 | private File existingFile; 18 | 19 | @Before 20 | public void setUp() throws Exception { 21 | URL filePath = PathResolverTest.class.getClassLoader().getResource("./test_controller.rb"); 22 | existingFile = new File(filePath.toURI()); 23 | String parentPath = existingFile.getParent(); 24 | 25 | this.sensorContext = SensorContextTester.create(new File(parentPath)); 26 | this.sensorContext.settings().setProperty("path key", "test_controller.rb"); 27 | 28 | DefaultInputFile file = 29 | new DefaultInputFile("", "test_controller.rb") 30 | .setLanguage(Ruby.LANGUAGE_KEY); 31 | 32 | this.sensorContext.fileSystem().add(file); 33 | 34 | this.resolver = new PathResolver(); 35 | } 36 | 37 | @Test 38 | public void returnsAbsolutePathToFile_ifSpecifiedAndExists() { 39 | String result = this.resolver.getPath(this.sensorContext, "path key", "not me"); 40 | assertSamePath(this.existingFile, result); 41 | } 42 | 43 | @Test 44 | public void returnsAbsolutePathToFallbackFile_ifPrimaryNotConfiguredAndFallbackExists() { 45 | String result = this.resolver.getPath(this.sensorContext, "new path key", "test_controller.rb"); 46 | assertSamePath(this.existingFile, result); 47 | } 48 | 49 | @Test 50 | public void returnsAbsolutePathToFallbackFile_ifPrimaryNotConfiguredButEmptyAndFallbackExists() { 51 | this.sensorContext.settings().setProperty("new path key", ""); 52 | String result = this.resolver.getPath(this.sensorContext, "new path key", "test_controller.rb"); 53 | assertSamePath(this.existingFile, result); 54 | } 55 | 56 | @Test 57 | public void returnsNull_ifPrimaryNotConfiguredAndFallbackNull() { 58 | String result = this.resolver.getPath(this.sensorContext, "new path key", null); 59 | assertThat(result).isNull(); 60 | } 61 | 62 | @Test 63 | public void returnsNull_ifRequestedPathDoesNotExist() { 64 | this.sensorContext.settings().setProperty("new path key", "missing.ts"); 65 | String result = this.resolver.getPath(this.sensorContext, "new path key", "test_controller.rb"); 66 | assertThat(result).isNull(); 67 | } 68 | 69 | @Test 70 | public void returnsAbsolutePathToFile_ifAlreadyAbsoluteAndExists() { 71 | this.sensorContext.settings().setProperty("new path key", this.existingFile.getAbsolutePath()); 72 | String result = this.resolver.getPath(this.sensorContext, "new path key", "not me"); 73 | assertSamePath(this.existingFile, result); 74 | } 75 | 76 | private void assertSamePath(File existingFile, String argument) { 77 | if (argument == null) { 78 | assertThat(existingFile).isNull(); 79 | } else { 80 | assertThat(existingFile).isEqualTo(new File(argument)); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/RubyPluginTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Test; 6 | import org.sonar.api.Plugin; 7 | import org.sonar.api.config.PropertyDefinition; 8 | import org.sonar.api.utils.Version; 9 | 10 | public class RubyPluginTest { 11 | 12 | @SuppressWarnings("unchecked") 13 | @Test 14 | public void testDefine() { 15 | RubyPlugin plugin = new RubyPlugin(); 16 | 17 | Plugin.Context context = new Plugin.Context(Version.create(1, 0)); 18 | 19 | plugin.define(context); 20 | 21 | assertThat(context.getExtensions().size()).isEqualTo(18); 22 | 23 | assertThat(context.getExtensions().stream().filter(ext -> ext instanceof PropertyDefinition).count()).isEqualTo(7); 24 | assertThat(context.getExtensions().stream().filter(ext -> ext instanceof Class).count()).isEqualTo(11); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/RubyRuleProfileTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Test; 6 | import org.sonar.api.profiles.RulesProfile; 7 | 8 | public class RubyRuleProfileTest { 9 | 10 | @Test 11 | public void testCreateProfile() { 12 | RubyRuleProfile rubyProfile = new RubyRuleProfile(); 13 | 14 | RulesProfile profile = rubyProfile.createProfile(null); 15 | 16 | assertThat(profile.getName()).isEqualTo("Rubocop"); 17 | assertThat(profile.getLanguage()).isEqualTo("rb"); 18 | assertThat(profile.getActiveRules().size()).isEqualTo(341); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/RubyRulesDefinitionTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import com.fortitudetec.sonar.plugins.ruby.model.RubocopRule; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.sonar.api.rule.Severity; 10 | import org.sonar.api.rules.RuleType; 11 | import org.sonar.api.server.rule.RulesDefinition; 12 | 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | public class RubyRulesDefinitionTest { 17 | 18 | private RubyRulesDefinition rules; 19 | 20 | @Before 21 | public void setUp() { 22 | rules = new RubyRulesDefinition(); 23 | } 24 | 25 | @Test 26 | public void testGetCoreRules() { 27 | List coreRules = rules.getCoreRules(); 28 | 29 | assertThat(coreRules.size()).isEqualTo(340); 30 | 31 | assertSeverityCount(Severity.BLOCKER, coreRules, 0); 32 | assertSeverityCount(Severity.CRITICAL, coreRules, 4); 33 | assertSeverityCount(Severity.MAJOR, coreRules, 0); 34 | assertSeverityCount(Severity.MINOR, coreRules, 324); 35 | assertSeverityCount(Severity.INFO, coreRules, 12); 36 | 37 | assertTypeCount(RuleType.CODE_SMELL, coreRules, 311); 38 | assertTypeCount(RuleType.VULNERABILITY, coreRules, 4); 39 | assertTypeCount(RuleType.BUG, coreRules, 25); 40 | } 41 | 42 | @Test 43 | public void testDefine() { 44 | RulesDefinition.Context context = new RulesDefinition.Context(); 45 | 46 | rules.define(context); 47 | 48 | assertThat(context.repositories().size()).isEqualTo(1); 49 | assertThat(context.repository("rubocop")).isNotNull(); 50 | assertThat(context.repository("rubocop").language()).isEqualTo("rb"); 51 | assertThat(context.repository("rubocop").rules().size()).isEqualTo(341); 52 | } 53 | 54 | 55 | private void assertSeverityCount(String severity, List rules, int expectedCount) { 56 | List filteredRules = rules.stream() 57 | .filter(rule -> severity.equals(rule.severity)) 58 | .collect(Collectors.toList()); 59 | 60 | assertThat(filteredRules.size()).isEqualTo(expectedCount); 61 | } 62 | 63 | private void assertTypeCount(RuleType type, List rules, int expectedCount) { 64 | List filteredRules = rules.stream() 65 | .filter(rule -> type.equals(rule.debtType)) 66 | .collect(Collectors.toList()); 67 | 68 | assertThat(filteredRules.size()).isEqualTo(expectedCount); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/RubyTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import com.google.common.collect.Lists; 6 | import org.junit.Test; 7 | import org.sonar.api.config.Settings; 8 | 9 | public class RubyTest { 10 | 11 | @Test 12 | public void testGetFileSuffixes_Default() { 13 | Ruby ruby = new Ruby(new Settings()); 14 | 15 | String[] fileSuffixes = ruby.getFileSuffixes(); 16 | 17 | assertThat(fileSuffixes).hasSize(8); 18 | assertThat(fileSuffixes).hasSameElementsAs(Lists.newArrayList("rb", "Gemfile", "gemspec", "rake", "spec", "Capfile", "ru", "Rakefile")); 19 | } 20 | 21 | @Test 22 | public void testGetFileSuffixes_Override() { 23 | Settings settings = new Settings(); 24 | settings.setProperty("sonar.ruby.file.suffixes", "rb"); 25 | 26 | Ruby ruby = new Ruby(settings); 27 | 28 | String[] fileSuffixes = ruby.getFileSuffixes(); 29 | 30 | assertThat(fileSuffixes).hasSize(1); 31 | assertThat(fileSuffixes).hasSameElementsAs(Lists.newArrayList("rb")); 32 | } 33 | 34 | @Test 35 | public void testGetFileSuffixes_WithSpaces() { 36 | Settings settings = new Settings(); 37 | settings.setProperty("sonar.ruby.file.suffixes", "rb, , gemspec"); 38 | 39 | Ruby ruby = new Ruby(settings); 40 | 41 | String[] fileSuffixes = ruby.getFileSuffixes(); 42 | 43 | assertThat(fileSuffixes).hasSize(2); 44 | assertThat(fileSuffixes).hasSameElementsAs(Lists.newArrayList("rb", "gemspec")); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/metrics/ClassCountParserTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.metrics; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Test; 6 | 7 | import java.io.File; 8 | import java.net.URISyntaxException; 9 | import java.net.URL; 10 | 11 | public class ClassCountParserTest { 12 | 13 | @Test 14 | public void testCountClasses() throws URISyntaxException { 15 | URL resource = ClassCountParser.class.getClassLoader().getResource("test_controller.rb"); 16 | int linesOfComment = ClassCountParser.countClasses(new File(resource.toURI())); 17 | assertThat(linesOfComment).isEqualTo(1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/metrics/CombinedCoverageSensorTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.metrics; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.Mockito.verify; 5 | 6 | import com.fortitudetec.sonar.plugins.ruby.Ruby; 7 | import com.fortitudetec.sonar.plugins.ruby.simplecov.SimpleCovSensor; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.mockito.Mock; 11 | import org.mockito.MockitoAnnotations; 12 | import org.sonar.api.batch.sensor.SensorContext; 13 | import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; 14 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 15 | 16 | import java.io.File; 17 | 18 | public class CombinedCoverageSensorTest { 19 | 20 | @Mock 21 | private RubyMetricsSensor locSensor; 22 | 23 | @Mock 24 | private SimpleCovSensor coverageSensor; 25 | 26 | @Before 27 | public void setup() { 28 | MockitoAnnotations.initMocks(this); 29 | } 30 | 31 | @Test 32 | public void testExecute() { 33 | CombinedCoverageSensor sensor = new CombinedCoverageSensor(locSensor, coverageSensor); 34 | 35 | SensorContextTester context = SensorContextTester.create(new File(".")); 36 | sensor.execute(context); 37 | 38 | verify(locSensor).execute(context); 39 | verify(coverageSensor).execute(context); 40 | } 41 | 42 | @Test 43 | public void testDescribe() { 44 | CombinedCoverageSensor sensor = new CombinedCoverageSensor(locSensor, coverageSensor); 45 | 46 | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); 47 | sensor.describe(descriptor); 48 | 49 | assertThat(descriptor.name()).isEqualTo("Combined SimpleCov and LOC sensor"); 50 | assertThat(descriptor.languages()).containsExactly(Ruby.LANGUAGE_KEY); 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/metrics/CommentCountParserTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.metrics; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Test; 6 | 7 | import java.io.File; 8 | import java.net.URISyntaxException; 9 | import java.net.URL; 10 | 11 | public class CommentCountParserTest { 12 | 13 | @Test 14 | public void testCountLinesOfComments() throws URISyntaxException { 15 | URL resource = CommentCountParserTest.class.getClassLoader().getResource("test_controller.rb"); 16 | int linesOfComment = CommentCountParser.countLinesOfComment(new File(resource.toURI())); 17 | assertThat(linesOfComment).isEqualTo(11); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/metrics/RubyMetricsSensorTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.metrics; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import com.fortitudetec.sonar.plugins.ruby.rubocop.RubocopSensorTest; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 9 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 10 | import org.sonar.api.batch.sensor.measure.Measure; 11 | import org.sonar.api.measures.CoreMetrics; 12 | 13 | import java.io.File; 14 | import java.net.URISyntaxException; 15 | import java.net.URL; 16 | import java.util.Collection; 17 | 18 | public class RubyMetricsSensorTest { 19 | 20 | private SensorContextTester context; 21 | private File rubyFile; 22 | 23 | @Before 24 | public void setUp() throws URISyntaxException { 25 | URL filePath = RubocopSensorTest.class.getClassLoader().getResource("./test_controller.rb"); 26 | rubyFile = new File(filePath.toURI()); 27 | 28 | context = SensorContextTester.create(rubyFile.getParentFile()); 29 | context.fileSystem().add(new DefaultInputFile("myProjectKey", "test_controller.rb") 30 | .setLanguage("rb") 31 | .setOriginalLineOffsets(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) 32 | .setLines(15)); 33 | } 34 | 35 | @Test 36 | public void testExecute() { 37 | RubyMetricsSensor sensor = new RubyMetricsSensor(); 38 | 39 | sensor.execute(context); 40 | 41 | Collection measures = context.measures("myProjectKey:test_controller.rb"); 42 | assertThat(measures.size()).isEqualTo(3); 43 | 44 | measures.forEach(measure -> { 45 | if (CoreMetrics.NCLOC.equals(measure.metric())) { 46 | assertThat(measure.value()).isEqualTo(4); 47 | } else if (CoreMetrics.COMMENT_LINES.equals(measure.metric())) { 48 | assertThat(measure.value()).isEqualTo(11); 49 | } else if (CoreMetrics.CLASSES.equals(measure.metric())) { 50 | assertThat(measure.value()).isEqualTo(1); 51 | } 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/rubocop/RubocopParserTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.rubocop; 2 | 3 | import static com.google.common.collect.Lists.newArrayList; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import com.fortitudetec.sonar.plugins.ruby.model.RubocopIssue; 7 | import com.google.common.base.Charsets; 8 | import com.google.common.io.Files; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.net.URISyntaxException; 16 | import java.net.URL; 17 | import java.nio.file.Paths; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | public class RubocopParserTest { 22 | 23 | private SensorContextTester context; 24 | 25 | @Before 26 | public void setUp() throws URISyntaxException { 27 | context = SensorContextTester.create(Paths.get(".")); 28 | } 29 | 30 | @Test 31 | public void testParseResultsFile() throws URISyntaxException, IOException { 32 | RubocopParser parser = new RubocopParser(); 33 | 34 | URL resource = RubocopParser.class.getClassLoader().getResource("result.json"); 35 | Map> results = parser.parse(Files.toString(new File(resource.toURI()), Charsets.UTF_8)); 36 | 37 | assertThat(results.size()).isEqualTo(14); 38 | assertThat(results.get("test/controllers/logging_controller_test.rb").size()).isEqualTo(2); 39 | assertThat(results.get("test/controllers/logging_controller_test.rb").get(0).getRuleName()).isEqualTo("Style/StringLiterals"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/rubocop/RubocopSensorTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.rubocop; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.Matchers.any; 5 | import static org.mockito.Matchers.isA; 6 | import static org.mockito.Mockito.when; 7 | 8 | import com.fortitudetec.sonar.plugins.ruby.PathResolver; 9 | import com.fortitudetec.sonar.plugins.ruby.Ruby; 10 | import com.fortitudetec.sonar.plugins.ruby.RubyPlugin; 11 | import com.fortitudetec.sonar.plugins.ruby.model.RubocopIssue; 12 | import com.fortitudetec.sonar.plugins.ruby.model.RubocopPosition; 13 | import com.google.common.collect.Lists; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | import org.mockito.Mock; 17 | import org.mockito.MockitoAnnotations; 18 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 19 | import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; 20 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 21 | import org.sonar.api.utils.System2; 22 | import org.sonar.api.utils.internal.JUnitTempFolder; 23 | 24 | import java.io.File; 25 | import java.net.URISyntaxException; 26 | import java.net.URL; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | public class RubocopSensorTest { 32 | 33 | private SensorContextTester context; 34 | private File rubyFile; 35 | 36 | @Mock 37 | private RubocopExecutor executor; 38 | 39 | @Mock 40 | private RubocopParser parser; 41 | 42 | @Before 43 | public void setUp() throws URISyntaxException { 44 | MockitoAnnotations.initMocks(this); 45 | 46 | URL filePath = RubocopSensorTest.class.getClassLoader().getResource("./test_controller.rb"); 47 | rubyFile = new File(filePath.toURI()); 48 | 49 | context = SensorContextTester.create(rubyFile.getParentFile()); 50 | context.fileSystem().add(new DefaultInputFile("myProjectKey", "test_controller.rb") 51 | .setLanguage("rb") 52 | .setOriginalLineOffsets(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) 53 | .setLines(15)); 54 | context.fileSystem().add(new DefaultInputFile("myProjectKey", "unknown_file.rb") 55 | .setLanguage("rb") 56 | .setOriginalLineOffsets(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) 57 | .setLines(10)); 58 | } 59 | 60 | @Test 61 | public void testDescribe() { 62 | RubocopExecutor executor = new RubocopExecutor(System2.INSTANCE, new JUnitTempFolder()); 63 | 64 | RubocopSensor sensor = new RubocopSensor(new PathResolver(), executor, new RubocopParser()); 65 | 66 | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); 67 | sensor.describe(descriptor); 68 | 69 | assertThat(descriptor.name()).isEqualTo("Linting sensor for Ruby files"); 70 | assertThat(descriptor.languages()).containsExactly(Ruby.LANGUAGE_KEY); 71 | } 72 | 73 | @Test 74 | public void testExecute_SkippingRubocop() { 75 | RubocopSensor sensor = new RubocopSensor(new PathResolver(), executor, parser); 76 | sensor.execute(context); 77 | 78 | assertThat(context.allIssues()).isEmpty(); 79 | } 80 | 81 | @Test 82 | public void testExecute() throws URISyntaxException { 83 | String reportPaths = "rubocop-result.json"; 84 | when(executor.execute(isA(RubocopExecutorConfig.class), any())).thenReturn(reportPaths); 85 | when(parser.parse(reportPaths)).thenReturn(buildIssues()); 86 | 87 | URL filePath = RubocopSensorTest.class.getClassLoader().getResource("./result.json"); 88 | File resultFile = new File(filePath.toURI()); 89 | context.settings().setProperty(RubyPlugin.RUBOCOP_REPORT_PATH, resultFile.getAbsolutePath()); 90 | 91 | RubocopSensor sensor = new RubocopSensor(new PathResolver(), executor, parser); 92 | sensor.execute(context); 93 | 94 | assertThat(context.allIssues().size()).isEqualTo(1); 95 | } 96 | 97 | private Map> buildIssues() { 98 | Map> issues = new HashMap<>(); 99 | 100 | issues.put(rubyFile.getAbsolutePath(), Lists.newArrayList( 101 | RubocopIssue.builder() 102 | .failure("Foo") 103 | .position(RubocopPosition.builder() 104 | .line(1) 105 | .character(1) 106 | .build()) 107 | .ruleName("Layout/AlignArray") 108 | .build() 109 | )); 110 | 111 | return issues; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/simplecov/SimpleCovParserTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.simplecov; 2 | 3 | import com.fortitudetec.sonar.plugins.ruby.RubyPlugin; 4 | import com.fortitudetec.sonar.plugins.ruby.utils.TestUtils; 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 9 | import org.sonar.api.batch.sensor.coverage.NewCoverage; 10 | import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage; 11 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.net.URISyntaxException; 16 | import java.net.URL; 17 | import java.util.Map; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | public class SimpleCovParserTest { 22 | 23 | private SensorContextTester context; 24 | private File rubyFile; 25 | 26 | @Before 27 | public void setUp() throws URISyntaxException { 28 | URL filePath = SimpleCovParserTest.class.getClassLoader().getResource("./test_controller.rb"); 29 | rubyFile = new File(filePath.toURI()); 30 | 31 | context = SensorContextTester.create(rubyFile.getParentFile()); 32 | context.settings().setProperty(RubyPlugin.TEST_FRAMEWORK, "RSpec"); 33 | context.fileSystem().add(new DefaultInputFile("myProjectKey", "test_controller.rb") 34 | .setLanguage("rb") 35 | .setLines(15)); 36 | 37 | TestUtils.buildResultSetFile(rubyFile); 38 | } 39 | 40 | @After 41 | public void tearDown() throws URISyntaxException { 42 | URL filePath = SimpleCovParserTest.class.getClassLoader().getResource(".resultset.json"); 43 | File resultFile = new File(filePath.toURI()); 44 | resultFile.delete(); 45 | } 46 | 47 | @Test 48 | public void testParse() throws URISyntaxException, IOException { 49 | SimpleCovParser parser = new SimpleCovParser(); 50 | 51 | URL filePath = SimpleCovParserTest.class.getClassLoader().getResource(".resultset.json"); 52 | File resultFile = new File(filePath.toURI()); 53 | 54 | Map coverage = parser.parse(context, resultFile); 55 | 56 | assertThat(coverage).isNotNull(); 57 | 58 | DefaultCoverage defaultCoverage = (DefaultCoverage) coverage.get(rubyFile.getAbsolutePath()); 59 | assertThat(defaultCoverage.coveredLines()).isEqualTo(4); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/simplecov/SimpleCovSensorTest.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.simplecov; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fortitudetec.sonar.plugins.ruby.RubyPlugin; 5 | import com.fortitudetec.sonar.plugins.ruby.utils.TestUtils; 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.sonar.api.batch.fs.internal.DefaultInputFile; 10 | import org.sonar.api.batch.sensor.coverage.CoverageType; 11 | import org.sonar.api.batch.sensor.internal.SensorContextTester; 12 | 13 | import java.io.File; 14 | import java.net.URISyntaxException; 15 | import java.net.URL; 16 | 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | 19 | public class SimpleCovSensorTest { 20 | 21 | private static final ObjectMapper MAPPER = new ObjectMapper(); 22 | 23 | private SensorContextTester context; 24 | private File rubyFile; 25 | 26 | @Before 27 | public void setUp() throws URISyntaxException { 28 | URL filePath = SimpleCovSensorTest.class.getClassLoader().getResource("./test_controller.rb"); 29 | rubyFile = new File(filePath.toURI()); 30 | 31 | context = SensorContextTester.create(rubyFile.getParentFile()); 32 | context.settings().setProperty(RubyPlugin.TEST_FRAMEWORK, "RSpec"); 33 | context.fileSystem().add(new DefaultInputFile("myProjectKey", "test_controller.rb") 34 | .setLanguage("rb") 35 | .setLines(15)); 36 | context.fileSystem().add(new DefaultInputFile("myProjectKey", "unknown_file.rb") 37 | .setLanguage("rb") 38 | .setLines(10)); 39 | 40 | TestUtils.buildResultSetFile(rubyFile); 41 | } 42 | 43 | @After 44 | public void tearDown() throws URISyntaxException { 45 | URL filePath = SimpleCovSensorTest.class.getClassLoader().getResource(".resultset.json"); 46 | File resultFile = new File(filePath.toURI()); 47 | resultFile.delete(); 48 | } 49 | 50 | @Test 51 | public void testExecute_EmptyReportPath() { 52 | SimpleCovSensor sensor = new SimpleCovSensor(); 53 | sensor.execute(context); 54 | 55 | assertThat(context.lineHits(rubyFile.getAbsolutePath(), CoverageType.UNIT, 1)).isNull(); 56 | } 57 | 58 | @Test 59 | public void testExecute() throws URISyntaxException { 60 | URL filePath = SimpleCovSensorTest.class.getClassLoader().getResource(".resultset.json"); 61 | File resultFile = new File(filePath.toURI()); 62 | context.settings().setProperty(RubyPlugin.SIMPLECOV_REPORT_PATH, resultFile.getAbsolutePath()); 63 | 64 | SimpleCovSensor sensor = new SimpleCovSensor(); 65 | sensor.execute(context); 66 | 67 | assertThat(context.lineHits("myProjectKey:test_controller.rb", CoverageType.UNIT, 1)).isEqualTo(1); 68 | } 69 | 70 | @Test 71 | public void testExecute_ZeroOutUnknownFiles() throws URISyntaxException { 72 | URL filePath = SimpleCovSensorTest.class.getClassLoader().getResource(".resultset.json"); 73 | File resultFile = new File(filePath.toURI()); 74 | context.settings().setProperty(RubyPlugin.SIMPLECOV_REPORT_PATH, resultFile.getAbsolutePath()); 75 | 76 | SimpleCovSensor sensor = new SimpleCovSensor(); 77 | sensor.execute(context); 78 | 79 | assertThat(context.lineHits("myProjectKey:unknown_file.rb", CoverageType.UNIT, 1)).isEqualTo(0); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/fortitudetec/sonar/plugins/ruby/utils/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.fortitudetec.sonar.plugins.ruby.utils; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Maps; 6 | import org.apache.commons.io.IOUtils; 7 | 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class TestUtils { 15 | 16 | private static final ObjectMapper MAPPER = new ObjectMapper(); 17 | 18 | public static void buildResultSetFile(File rubyFile) { 19 | Map results = new HashMap<>(); 20 | 21 | Map coverage = Maps.newHashMap(); 22 | List lines = Lists.newArrayList(1, null, null, 1, null, 1, null, null, null, null, null, null, null, null, 1); 23 | coverage.put(rubyFile.getAbsolutePath(), lines); 24 | 25 | Map testType = Maps.newHashMap(); 26 | testType.put("coverage", coverage); 27 | testType.put("timestamp", 1505253204); 28 | 29 | results.put("RSpec", testType); 30 | 31 | try (FileOutputStream fileOut = new FileOutputStream(rubyFile.getParent() + File.separatorChar + ".resultset.json")) { 32 | IOUtils.write(MAPPER.writeValueAsString(results), fileOut); 33 | } catch (Exception e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/resources/result.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "rubocop_version": "0.37.0", 4 | "ruby_engine": "ruby", 5 | "ruby_version": "2.3.0", 6 | "ruby_patchlevel": "0", 7 | "ruby_platform": "x86_64-darwin15" 8 | }, 9 | "files": [ 10 | { 11 | "path": "config.ru", 12 | "offenses": [] 13 | }, 14 | { 15 | "path": "Gemfile", 16 | "offenses": [ 17 | { 18 | "severity": "convention", 19 | "message": "Extra blank line detected.", 20 | "cop_name": "Style/EmptyLines", 21 | "corrected": false, 22 | "location": { 23 | "line": 3, 24 | "column": 1, 25 | "length": 1 26 | } 27 | }, 28 | { 29 | "severity": "convention", 30 | "message": "Missing space after #.", 31 | "cop_name": "Style/LeadingCommentSpace", 32 | "corrected": false, 33 | "location": { 34 | "line": 16, 35 | "column": 1, 36 | "length": 37 37 | } 38 | }, 39 | { 40 | "severity": "convention", 41 | "message": "Missing space after #.", 42 | "cop_name": "Style/LeadingCommentSpace", 43 | "corrected": false, 44 | "location": { 45 | "line": 18, 46 | "column": 1, 47 | "length": 46 48 | } 49 | }, 50 | { 51 | "severity": "convention", 52 | "message": "Missing space after #.", 53 | "cop_name": "Style/LeadingCommentSpace", 54 | "corrected": false, 55 | "location": { 56 | "line": 19, 57 | "column": 1, 58 | "length": 30 59 | } 60 | } 61 | ] 62 | }, 63 | { 64 | "path": "Rakefile", 65 | "offenses": [] 66 | }, 67 | { 68 | "path": "app/channels/application_cable/channel.rb", 69 | "offenses": [ 70 | { 71 | "severity": "convention", 72 | "message": "Line is too long. [125/120]", 73 | "cop_name": "Metrics/LineLength", 74 | "corrected": null, 75 | "location": { 76 | "line": 1, 77 | "column": 121, 78 | "length": 5 79 | } 80 | } 81 | ] 82 | }, 83 | { 84 | "path": "app/channels/application_cable/connection.rb", 85 | "offenses": [ 86 | { 87 | "severity": "convention", 88 | "message": "Line is too long. [125/120]", 89 | "cop_name": "Metrics/LineLength", 90 | "corrected": null, 91 | "location": { 92 | "line": 1, 93 | "column": 121, 94 | "length": 5 95 | } 96 | } 97 | ] 98 | }, 99 | { 100 | "path": "app/controllers/application_controller.rb", 101 | "offenses": [] 102 | }, 103 | { 104 | "path": "app/controllers/dashboard_controller.rb", 105 | "offenses": [ 106 | { 107 | "severity": "convention", 108 | "message": "Extra empty line detected at method body beginning.", 109 | "cop_name": "Style/EmptyLinesAroundMethodBody", 110 | "corrected": false, 111 | "location": { 112 | "line": 3, 113 | "column": 1, 114 | "length": 1 115 | } 116 | } 117 | ] 118 | }, 119 | { 120 | "path": "app/controllers/logging_controller.rb", 121 | "offenses": [ 122 | { 123 | "severity": "convention", 124 | "message": "Prefer single-quoted strings when you don't need string interpolation or special symbols.", 125 | "cop_name": "Style/StringLiterals", 126 | "corrected": false, 127 | "location": { 128 | "line": 6, 129 | "column": 25, 130 | "length": 12 131 | } 132 | }, 133 | { 134 | "severity": "convention", 135 | "message": "Prefer single-quoted strings when you don't need string interpolation or special symbols.", 136 | "cop_name": "Style/StringLiterals", 137 | "corrected": false, 138 | "location": { 139 | "line": 9, 140 | "column": 35, 141 | "length": 6 142 | } 143 | }, 144 | { 145 | "severity": "convention", 146 | "message": "Space found before comma.", 147 | "cop_name": "Style/SpaceBeforeComma", 148 | "corrected": false, 149 | "location": { 150 | "line": 10, 151 | "column": 61, 152 | "length": 1 153 | } 154 | } 155 | ] 156 | }, 157 | { 158 | "path": "app/helpers/application_helper.rb", 159 | "offenses": [] 160 | }, 161 | { 162 | "path": "app/helpers/logging_helper.rb", 163 | "offenses": [] 164 | }, 165 | { 166 | "path": "app/jobs/application_job.rb", 167 | "offenses": [] 168 | }, 169 | { 170 | "path": "app/mailers/application_mailer.rb", 171 | "offenses": [] 172 | }, 173 | { 174 | "path": "app/middleware/logging_backend.rb", 175 | "offenses": [ 176 | { 177 | "severity": "convention", 178 | "message": "Surrounding space missing for operator `=`.", 179 | "cop_name": "Style/SpaceAroundOperators", 180 | "corrected": false, 181 | "location": { 182 | "line": 4, 183 | "column": 26, 184 | "length": 1 185 | } 186 | }, 187 | { 188 | "severity": "convention", 189 | "message": "Use empty lines between method definitions.", 190 | "cop_name": "Style/EmptyLineBetweenDefs", 191 | "corrected": false, 192 | "location": { 193 | "line": 16, 194 | "column": 5, 195 | "length": 3 196 | } 197 | }, 198 | { 199 | "severity": "convention", 200 | "message": "Missing space after #.", 201 | "cop_name": "Style/LeadingCommentSpace", 202 | "corrected": false, 203 | "location": { 204 | "line": 27, 205 | "column": 25, 206 | "length": 8 207 | } 208 | }, 209 | { 210 | "severity": "convention", 211 | "message": "Method has too many lines. [29/20]", 212 | "cop_name": "Metrics/MethodLength", 213 | "corrected": null, 214 | "location": { 215 | "line": 29, 216 | "column": 5, 217 | "length": 3 218 | } 219 | }, 220 | { 221 | "severity": "convention", 222 | "message": "Missing space after #.", 223 | "cop_name": "Style/LeadingCommentSpace", 224 | "corrected": false, 225 | "location": { 226 | "line": 30, 227 | "column": 7, 228 | "length": 68 229 | } 230 | }, 231 | { 232 | "severity": "convention", 233 | "message": "Missing space after #.", 234 | "cop_name": "Style/LeadingCommentSpace", 235 | "corrected": false, 236 | "location": { 237 | "line": 31, 238 | "column": 7, 239 | "length": 53 240 | } 241 | }, 242 | { 243 | "severity": "convention", 244 | "message": "Inconsistent indentation detected.", 245 | "cop_name": "Style/IndentationConsistency", 246 | "corrected": false, 247 | "location": { 248 | "line": 34, 249 | "column": 13, 250 | "length": 1306 251 | } 252 | }, 253 | { 254 | "severity": "convention", 255 | "message": "Use the new Ruby 1.9 hash syntax.", 256 | "cop_name": "Style/HashSyntax", 257 | "corrected": false, 258 | "location": { 259 | "line": 35, 260 | "column": 31, 261 | "length": 8 262 | } 263 | }, 264 | { 265 | "severity": "convention", 266 | "message": "Use the new Ruby 1.9 hash syntax.", 267 | "cop_name": "Style/HashSyntax", 268 | "corrected": false, 269 | "location": { 270 | "line": 35, 271 | "column": 76, 272 | "length": 8 273 | } 274 | }, 275 | { 276 | "severity": "convention", 277 | "message": "Use the new Ruby 1.9 hash syntax.", 278 | "cop_name": "Style/HashSyntax", 279 | "corrected": false, 280 | "location": { 281 | "line": 35, 282 | "column": 91, 283 | "length": 11 284 | } 285 | }, 286 | { 287 | "severity": "convention", 288 | "message": "Do not write to stdout. Use Rails' logger if you want to log.", 289 | "cop_name": "Rails/Output", 290 | "corrected": null, 291 | "location": { 292 | "line": 37, 293 | "column": 11, 294 | "length": 1 295 | } 296 | }, 297 | { 298 | "severity": "warning", 299 | "message": "Unused block argument - `pchannel`. If it's necessary, use `_` or `_pchannel` as an argument name to indicate that it won't be used.", 300 | "cop_name": "Lint/UnusedBlockArgument", 301 | "corrected": false, 302 | "location": { 303 | "line": 38, 304 | "column": 27, 305 | "length": 8 306 | } 307 | }, 308 | { 309 | "severity": "convention", 310 | "message": "Do not write to stdout. Use Rails' logger if you want to log.", 311 | "cop_name": "Rails/Output", 312 | "corrected": null, 313 | "location": { 314 | "line": 40, 315 | "column": 13, 316 | "length": 1 317 | } 318 | }, 319 | { 320 | "severity": "convention", 321 | "message": "Missing space after #.", 322 | "cop_name": "Style/LeadingCommentSpace", 323 | "corrected": false, 324 | "location": { 325 | "line": 42, 326 | "column": 15, 327 | "length": 66 328 | } 329 | }, 330 | { 331 | "severity": "convention", 332 | "message": "Use 2 (not -12) spaces for indentation.", 333 | "cop_name": "Style/IndentationWidth", 334 | "corrected": false, 335 | "location": { 336 | "line": 43, 337 | "column": 15, 338 | "length": 12 339 | } 340 | }, 341 | { 342 | "severity": "convention", 343 | "message": "Do not write to stdout. Use Rails' logger if you want to log.", 344 | "cop_name": "Rails/Output", 345 | "corrected": null, 346 | "location": { 347 | "line": 44, 348 | "column": 17, 349 | "length": 1 350 | } 351 | }, 352 | { 353 | "severity": "convention", 354 | "message": "Use the new Ruby 1.9 hash syntax.", 355 | "cop_name": "Style/HashSyntax", 356 | "corrected": false, 357 | "location": { 358 | "line": 47, 359 | "column": 19, 360 | "length": 8 361 | } 362 | }, 363 | { 364 | "severity": "convention", 365 | "message": "Use the new Ruby 1.9 hash syntax.", 366 | "cop_name": "Style/HashSyntax", 367 | "corrected": false, 368 | "location": { 369 | "line": 48, 370 | "column": 19, 371 | "length": 11 372 | } 373 | }, 374 | { 375 | "severity": "convention", 376 | "message": "Use the new Ruby 1.9 hash syntax.", 377 | "cop_name": "Style/HashSyntax", 378 | "corrected": false, 379 | "location": { 380 | "line": 49, 381 | "column": 19, 382 | "length": 8 383 | } 384 | }, 385 | { 386 | "severity": "convention", 387 | "message": "Missing space after #.", 388 | "cop_name": "Style/LeadingCommentSpace", 389 | "corrected": false, 390 | "location": { 391 | "line": 52, 392 | "column": 15, 393 | "length": 66 394 | } 395 | }, 396 | { 397 | "severity": "convention", 398 | "message": "Incorrect indentation detected (column 14 instead of 16).", 399 | "cop_name": "Style/CommentIndentation", 400 | "corrected": false, 401 | "location": { 402 | "line": 53, 403 | "column": 15, 404 | "length": 35 405 | } 406 | }, 407 | { 408 | "severity": "convention", 409 | "message": "Missing space after #.", 410 | "cop_name": "Style/LeadingCommentSpace", 411 | "corrected": false, 412 | "location": { 413 | "line": 53, 414 | "column": 15, 415 | "length": 35 416 | } 417 | }, 418 | { 419 | "severity": "convention", 420 | "message": "Do not write to stdout. Use Rails' logger if you want to log.", 421 | "cop_name": "Rails/Output", 422 | "corrected": null, 423 | "location": { 424 | "line": 54, 425 | "column": 17, 426 | "length": 1 427 | } 428 | }, 429 | { 430 | "severity": "convention", 431 | "message": "Inconsistent indentation detected.", 432 | "cop_name": "Style/IndentationConsistency", 433 | "corrected": false, 434 | "location": { 435 | "line": 54, 436 | "column": 17, 437 | "length": 42 438 | } 439 | }, 440 | { 441 | "severity": "convention", 442 | "message": "Inconsistent indentation detected.", 443 | "cop_name": "Style/IndentationConsistency", 444 | "corrected": false, 445 | "location": { 446 | "line": 55, 447 | "column": 17, 448 | "length": 129 449 | } 450 | }, 451 | { 452 | "severity": "convention", 453 | "message": "Use the new Ruby 1.9 hash syntax.", 454 | "cop_name": "Style/HashSyntax", 455 | "corrected": false, 456 | "location": { 457 | "line": 56, 458 | "column": 19, 459 | "length": 8 460 | } 461 | }, 462 | { 463 | "severity": "convention", 464 | "message": "Use the new Ruby 1.9 hash syntax.", 465 | "cop_name": "Style/HashSyntax", 466 | "corrected": false, 467 | "location": { 468 | "line": 57, 469 | "column": 19, 470 | "length": 7 471 | } 472 | }, 473 | { 474 | "severity": "convention", 475 | "message": "Incorrect indentation detected (column 14 instead of 28).", 476 | "cop_name": "Style/CommentIndentation", 477 | "corrected": false, 478 | "location": { 479 | "line": 59, 480 | "column": 15, 481 | "length": 4 482 | } 483 | }, 484 | { 485 | "severity": "convention", 486 | "message": "Missing space after #.", 487 | "cop_name": "Style/LeadingCommentSpace", 488 | "corrected": false, 489 | "location": { 490 | "line": 59, 491 | "column": 15, 492 | "length": 4 493 | } 494 | }, 495 | { 496 | "severity": "warning", 497 | "message": "`end` at 60, 26 is not aligned with `@clients.each do |log_socket|` at 41, 12.", 498 | "cop_name": "Lint/BlockAlignment", 499 | "corrected": false, 500 | "location": { 501 | "line": 60, 502 | "column": 27, 503 | "length": 3 504 | } 505 | }, 506 | { 507 | "severity": "convention", 508 | "message": "Do not write to stdout. Use Rails' logger if you want to log.", 509 | "cop_name": "Rails/Output", 510 | "corrected": null, 511 | "location": { 512 | "line": 61, 513 | "column": 13, 514 | "length": 1 515 | } 516 | }, 517 | { 518 | "severity": "warning", 519 | "message": "`end` at 64, 6 is not aligned with `Thread.new do` at 34, 12.", 520 | "cop_name": "Lint/BlockAlignment", 521 | "corrected": false, 522 | "location": { 523 | "line": 64, 524 | "column": 7, 525 | "length": 3 526 | } 527 | }, 528 | { 529 | "severity": "convention", 530 | "message": "Missing space after #.", 531 | "cop_name": "Style/LeadingCommentSpace", 532 | "corrected": false, 533 | "location": { 534 | "line": 68, 535 | "column": 7, 536 | "length": 73 537 | } 538 | }, 539 | { 540 | "severity": "convention", 541 | "message": "Missing space after #.", 542 | "cop_name": "Style/LeadingCommentSpace", 543 | "corrected": false, 544 | "location": { 545 | "line": 69, 546 | "column": 7, 547 | "length": 70 548 | } 549 | }, 550 | { 551 | "severity": "convention", 552 | "message": "Missing space after #.", 553 | "cop_name": "Style/LeadingCommentSpace", 554 | "corrected": false, 555 | "location": { 556 | "line": 70, 557 | "column": 7, 558 | "length": 23 559 | } 560 | }, 561 | { 562 | "severity": "convention", 563 | "message": "Use a guard clause instead of wrapping the code inside a conditional expression.", 564 | "cop_name": "Style/GuardClause", 565 | "corrected": null, 566 | "location": { 567 | "line": 71, 568 | "column": 7, 569 | "length": 2 570 | } 571 | }, 572 | { 573 | "severity": "convention", 574 | "message": "Method has too many lines. [26/20]", 575 | "cop_name": "Metrics/MethodLength", 576 | "corrected": null, 577 | "location": { 578 | "line": 79, 579 | "column": 5, 580 | "length": 3 581 | } 582 | }, 583 | { 584 | "severity": "convention", 585 | "message": "Missing space after #.", 586 | "cop_name": "Style/LeadingCommentSpace", 587 | "corrected": false, 588 | "location": { 589 | "line": 80, 590 | "column": 7, 591 | "length": 71 592 | } 593 | }, 594 | { 595 | "severity": "convention", 596 | "message": "Missing space after #.", 597 | "cop_name": "Style/LeadingCommentSpace", 598 | "corrected": false, 599 | "location": { 600 | "line": 81, 601 | "column": 7, 602 | "length": 61 603 | } 604 | }, 605 | { 606 | "severity": "convention", 607 | "message": "Redundant curly braces around a hash parameter.", 608 | "cop_name": "Style/BracesAroundHashParameters", 609 | "corrected": false, 610 | "location": { 611 | "line": 82, 612 | "column": 42, 613 | "length": 22 614 | } 615 | }, 616 | { 617 | "severity": "convention", 618 | "message": "Space inside { missing.", 619 | "cop_name": "Style/SpaceInsideHashLiteralBraces", 620 | "corrected": false, 621 | "location": { 622 | "line": 82, 623 | "column": 42, 624 | "length": 1 625 | } 626 | }, 627 | { 628 | "severity": "convention", 629 | "message": "Space inside } missing.", 630 | "cop_name": "Style/SpaceInsideHashLiteralBraces", 631 | "corrected": false, 632 | "location": { 633 | "line": 82, 634 | "column": 63, 635 | "length": 1 636 | } 637 | }, 638 | { 639 | "severity": "warning", 640 | "message": "Unused block argument - `event`. You can omit the argument if you don't care about it.", 641 | "cop_name": "Lint/UnusedBlockArgument", 642 | "corrected": false, 643 | "location": { 644 | "line": 84, 645 | "column": 23, 646 | "length": 5 647 | } 648 | }, 649 | { 650 | "severity": "convention", 651 | "message": "Do not write to stdout. Use Rails' logger if you want to log.", 652 | "cop_name": "Rails/Output", 653 | "corrected": null, 654 | "location": { 655 | "line": 85, 656 | "column": 9, 657 | "length": 1 658 | } 659 | }, 660 | { 661 | "severity": "convention", 662 | "message": "Do not write to stdout. Use Rails' logger if you want to log.", 663 | "cop_name": "Rails/Output", 664 | "corrected": null, 665 | "location": { 666 | "line": 89, 667 | "column": 9, 668 | "length": 1 669 | } 670 | }, 671 | { 672 | "severity": "convention", 673 | "message": "`Hash#has_key?` is deprecated in favor of `Hash#key?`.", 674 | "cop_name": "Style/DeprecatedHashMethods", 675 | "corrected": false, 676 | "location": { 677 | "line": 96, 678 | "column": 16, 679 | "length": 8 680 | } 681 | }, 682 | { 683 | "severity": "convention", 684 | "message": "Space found before comma.", 685 | "cop_name": "Style/SpaceBeforeComma", 686 | "corrected": false, 687 | "location": { 688 | "line": 100, 689 | "column": 53, 690 | "length": 1 691 | } 692 | }, 693 | { 694 | "severity": "convention", 695 | "message": "Space found before comma.", 696 | "cop_name": "Style/SpaceBeforeComma", 697 | "corrected": false, 698 | "location": { 699 | "line": 100, 700 | "column": 57, 701 | "length": 1 702 | } 703 | }, 704 | { 705 | "severity": "convention", 706 | "message": "Use the new Ruby 1.9 hash syntax.", 707 | "cop_name": "Style/HashSyntax", 708 | "corrected": false, 709 | "location": { 710 | "line": 102, 711 | "column": 13, 712 | "length": 8 713 | } 714 | }, 715 | { 716 | "severity": "convention", 717 | "message": "Use the new Ruby 1.9 hash syntax.", 718 | "cop_name": "Style/HashSyntax", 719 | "corrected": false, 720 | "location": { 721 | "line": 103, 722 | "column": 13, 723 | "length": 8 724 | } 725 | }, 726 | { 727 | "severity": "convention", 728 | "message": "Space missing to the left of {.", 729 | "cop_name": "Style/SpaceBeforeBlockBraces", 730 | "corrected": false, 731 | "location": { 732 | "line": 103, 733 | "column": 30, 734 | "length": 1 735 | } 736 | }, 737 | { 738 | "severity": "convention", 739 | "message": "Space between { and | missing.", 740 | "cop_name": "Style/SpaceInsideBlockBraces", 741 | "corrected": false, 742 | "location": { 743 | "line": 103, 744 | "column": 30, 745 | "length": 2 746 | } 747 | }, 748 | { 749 | "severity": "convention", 750 | "message": "Space missing inside }.", 751 | "cop_name": "Style/SpaceInsideBlockBraces", 752 | "corrected": false, 753 | "location": { 754 | "line": 103, 755 | "column": 48, 756 | "length": 1 757 | } 758 | }, 759 | { 760 | "severity": "convention", 761 | "message": "Redundant `return` detected.", 762 | "cop_name": "Style/RedundantReturn", 763 | "corrected": false, 764 | "location": { 765 | "line": 107, 766 | "column": 7, 767 | "length": 6 768 | } 769 | } 770 | ] 771 | }, 772 | { 773 | "path": "app/models/application_record.rb", 774 | "offenses": [] 775 | }, 776 | { 777 | "path": "bin/bundle", 778 | "offenses": [] 779 | }, 780 | { 781 | "path": "bin/rails", 782 | "offenses": [] 783 | }, 784 | { 785 | "path": "bin/rake", 786 | "offenses": [] 787 | }, 788 | { 789 | "path": "bin/setup", 790 | "offenses": [ 791 | { 792 | "severity": "convention", 793 | "message": "Use `||` instead of `or`.", 794 | "cop_name": "Style/AndOr", 795 | "corrected": false, 796 | "location": { 797 | "line": 19, 798 | "column": 26, 799 | "length": 2 800 | } 801 | } 802 | ] 803 | }, 804 | { 805 | "path": "bin/spring", 806 | "offenses": [] 807 | }, 808 | { 809 | "path": "bin/update", 810 | "offenses": [ 811 | { 812 | "severity": "convention", 813 | "message": "Use `||` instead of `or`.", 814 | "cop_name": "Style/AndOr", 815 | "corrected": false, 816 | "location": { 817 | "line": 19, 818 | "column": 25, 819 | "length": 2 820 | } 821 | } 822 | ] 823 | }, 824 | { 825 | "path": "config/application.rb", 826 | "offenses": [ 827 | { 828 | "severity": "convention", 829 | "message": "Missing space after #.", 830 | "cop_name": "Style/LeadingCommentSpace", 831 | "corrected": false, 832 | "location": { 833 | "line": 14, 834 | "column": 5, 835 | "length": 27 836 | } 837 | }, 838 | { 839 | "severity": "convention", 840 | "message": "Missing space after #.", 841 | "cop_name": "Style/LeadingCommentSpace", 842 | "corrected": false, 843 | "location": { 844 | "line": 15, 845 | "column": 5, 846 | "length": 41 847 | } 848 | } 849 | ] 850 | }, 851 | { 852 | "path": "config/boot.rb", 853 | "offenses": [] 854 | }, 855 | { 856 | "path": "config/environment.rb", 857 | "offenses": [] 858 | }, 859 | { 860 | "path": "config/environments/development.rb", 861 | "offenses": [] 862 | }, 863 | { 864 | "path": "config/environments/production.rb", 865 | "offenses": [ 866 | { 867 | "severity": "convention", 868 | "message": "Space inside square brackets detected.", 869 | "cop_name": "Style/SpaceInsideBrackets", 870 | "corrected": false, 871 | "location": { 872 | "line": 53, 873 | "column": 22, 874 | "length": 1 875 | } 876 | }, 877 | { 878 | "severity": "convention", 879 | "message": "Space inside square brackets detected.", 880 | "cop_name": "Style/SpaceInsideBrackets", 881 | "corrected": false, 882 | "location": { 883 | "line": 53, 884 | "column": 34, 885 | "length": 1 886 | } 887 | } 888 | ] 889 | }, 890 | { 891 | "path": "config/environments/test.rb", 892 | "offenses": [] 893 | }, 894 | { 895 | "path": "config/initializers/active_record_belongs_to_required_by_default.rb", 896 | "offenses": [] 897 | }, 898 | { 899 | "path": "config/initializers/application_controller_renderer.rb", 900 | "offenses": [] 901 | }, 902 | { 903 | "path": "config/initializers/assets.rb", 904 | "offenses": [] 905 | }, 906 | { 907 | "path": "config/initializers/backtrace_silencers.rb", 908 | "offenses": [] 909 | }, 910 | { 911 | "path": "config/initializers/callback_terminator.rb", 912 | "offenses": [] 913 | }, 914 | { 915 | "path": "config/initializers/cookies_serializer.rb", 916 | "offenses": [] 917 | }, 918 | { 919 | "path": "config/initializers/filter_parameter_logging.rb", 920 | "offenses": [] 921 | }, 922 | { 923 | "path": "config/initializers/inflections.rb", 924 | "offenses": [] 925 | }, 926 | { 927 | "path": "config/initializers/mime_types.rb", 928 | "offenses": [] 929 | }, 930 | { 931 | "path": "config/initializers/per_form_csrf_tokens.rb", 932 | "offenses": [] 933 | }, 934 | { 935 | "path": "config/initializers/redis.rb", 936 | "offenses": [ 937 | { 938 | "severity": "convention", 939 | "message": "Use the new Ruby 1.9 hash syntax.", 940 | "cop_name": "Style/HashSyntax", 941 | "corrected": false, 942 | "location": { 943 | "line": 1, 944 | "column": 20, 945 | "length": 8 946 | } 947 | }, 948 | { 949 | "severity": "convention", 950 | "message": "Use the new Ruby 1.9 hash syntax.", 951 | "cop_name": "Style/HashSyntax", 952 | "corrected": false, 953 | "location": { 954 | "line": 1, 955 | "column": 65, 956 | "length": 8 957 | } 958 | } 959 | ] 960 | }, 961 | { 962 | "path": "config/initializers/request_forgery_protection.rb", 963 | "offenses": [] 964 | }, 965 | { 966 | "path": "config/initializers/session_store.rb", 967 | "offenses": [] 968 | }, 969 | { 970 | "path": "config/initializers/wrap_parameters.rb", 971 | "offenses": [] 972 | }, 973 | { 974 | "path": "config/puma.rb", 975 | "offenses": [ 976 | { 977 | "severity": "convention", 978 | "message": "Prefer single-quoted strings when you don't need string interpolation or special symbols.", 979 | "cop_name": "Style/StringLiterals", 980 | "corrected": false, 981 | "location": { 982 | "line": 7, 983 | "column": 27, 984 | "length": 19 985 | } 986 | }, 987 | { 988 | "severity": "convention", 989 | "message": "Prefer single-quoted strings when you don't need string interpolation or special symbols.", 990 | "cop_name": "Style/StringLiterals", 991 | "corrected": false, 992 | "location": { 993 | "line": 12, 994 | "column": 23, 995 | "length": 6 996 | } 997 | }, 998 | { 999 | "severity": "convention", 1000 | "message": "Prefer single-quoted strings when you don't need string interpolation or special symbols.", 1001 | "cop_name": "Style/StringLiterals", 1002 | "corrected": false, 1003 | "location": { 1004 | "line": 16, 1005 | "column": 23, 1006 | "length": 11 1007 | } 1008 | }, 1009 | { 1010 | "severity": "convention", 1011 | "message": "Prefer single-quoted strings when you don't need string interpolation or special symbols.", 1012 | "cop_name": "Style/StringLiterals", 1013 | "corrected": false, 1014 | "location": { 1015 | "line": 16, 1016 | "column": 38, 1017 | "length": 13 1018 | } 1019 | } 1020 | ] 1021 | }, 1022 | { 1023 | "path": "config/routes.rb", 1024 | "offenses": [] 1025 | }, 1026 | { 1027 | "path": "test/controllers/logging_controller_test.rb", 1028 | "offenses": [ 1029 | { 1030 | "severity": "convention", 1031 | "message": "Prefer single-quoted strings when you don't need string interpolation or special symbols.", 1032 | "cop_name": "Style/StringLiterals", 1033 | "corrected": false, 1034 | "location": { 1035 | "line": 4, 1036 | "column": 8, 1037 | "length": 18 1038 | } 1039 | }, 1040 | { 1041 | "severity": "convention", 1042 | "message": "Extra empty line detected at class body end.", 1043 | "cop_name": "Style/EmptyLinesAroundClassBody", 1044 | "corrected": false, 1045 | "location": { 1046 | "line": 8, 1047 | "column": 1, 1048 | "length": 1 1049 | } 1050 | } 1051 | ] 1052 | }, 1053 | { 1054 | "path": "test/test_helper.rb", 1055 | "offenses": [ 1056 | { 1057 | "severity": "convention", 1058 | "message": "Use nested module/class definitions instead of compact style.", 1059 | "cop_name": "Style/ClassAndModuleChildren", 1060 | "corrected": null, 1061 | "location": { 1062 | "line": 5, 1063 | "column": 7, 1064 | "length": 23 1065 | } 1066 | } 1067 | ] 1068 | } 1069 | ], 1070 | "summary": { 1071 | "offense_count": 79, 1072 | "target_file_count": 44, 1073 | "inspected_file_count": 44 1074 | } 1075 | } -------------------------------------------------------------------------------- /src/test/resources/test_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | # before_action :login 6 | before_action :authenticate_user! 7 | # helper_method :current_user 8 | # def login 9 | # @user = User.new 10 | # # @user.role = params[:fake_role].blank? ? :client : params[:fake_role].to_sym 11 | # end 12 | # def current_user 13 | # @user 14 | # end 15 | end -------------------------------------------------------------------------------- /src/test/resources/unknown_file.rb: -------------------------------------------------------------------------------- 1 | puts "I am an unknown file" 2 | 3 | # This is a comment 4 | 5 | puts "Just trying to get to 10 lines" 6 | 7 | a = 1 + 2 8 | puts a 9 | b = !false 10 | puts "It's funny cause it's true" --------------------------------------------------------------------------------