├── .github └── workflows │ ├── rubocop-analysis.yml │ └── ruby.yml ├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── code-scanning-rubocop.gemspec ├── entrypoint.sh ├── lib ├── code_scanning.rb └── code_scanning │ ├── rubocop │ ├── rule.rb │ ├── sarif_formatter.rb │ └── version.rb │ └── rules_generator.rb ├── preview.png ├── rubocop-action └── action.yml └── test ├── code_scanning └── rubocop_test.rb └── test_helper.rb /.github/workflows/rubocop-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "RuboCop" 2 | 3 | on: [push] 4 | 5 | jobs: 6 | rubocop_job: 7 | runs-on: ubuntu-latest 8 | name: Code Scanning job run 9 | strategy: 10 | fail-fast: false 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up Ruby 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: 2.6 20 | 21 | - name: Install dependencies 22 | run: bundle install 23 | 24 | - name: RuboCop run 25 | run: | 26 | bash -c " 27 | bundle exec rubocop --require code_scanning --format CodeScanning::SarifFormatter -o rubocop.sarif 28 | [[ $? -ne 2 ]] 29 | " 30 | 31 | - name: Upload Sarif output 32 | uses: github/codeql-action/upload-sarif@v1 33 | with: 34 | sarif_file: rubocop.sarif 35 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Ruby 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: 2.6 20 | - name: Install dependencies 21 | run: bundle install 22 | - name: Run tests 23 | run: bundle exec rake 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | # Used by dotenv library to load environment variables. 14 | # .env 15 | 16 | # Ignore Byebug command history file. 17 | .byebug_history 18 | 19 | ## Specific to RubyMotion: 20 | .dat* 21 | .repl_history 22 | build/ 23 | *.bridgesupport 24 | build-iPhoneOS/ 25 | build-iPhoneSimulator/ 26 | 27 | ## Specific to RubyMotion (use of CocoaPods): 28 | # 29 | # We recommend against adding the Pods directory to your .gitignore. However 30 | # you should judge for yourself, the pros and cons are mentioned at: 31 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 32 | # 33 | # vendor/Pods/ 34 | 35 | ## Documentation cache and generated files: 36 | /.yardoc/ 37 | /_yardoc/ 38 | /doc/ 39 | /rdoc/ 40 | 41 | ## Environment normalization: 42 | /.bundle/ 43 | /vendor/bundle 44 | /lib/bundler/man/ 45 | 46 | # for a library or gem, you might want to ignore these files since the code is 47 | # intended to run in multiple environments; otherwise, check them in: 48 | # Gemfile.lock 49 | # .ruby-version 50 | # .ruby-gemset 51 | 52 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 53 | .rvmrc 54 | 55 | # Used by RuboCop. Remote config files pulled in from inherit_from directive. 56 | # .rubocop-https?--* 57 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | # The behavior of RuboCop can be controlled via the .rubocop.yml 4 | # configuration file. It makes it possible to enable/disable 5 | # certain cops (checks) and to alter their behavior if they accept 6 | # any parameters. The file can be placed either in your home 7 | # directory or in some project directory. 8 | # 9 | # RuboCop will start looking for the configuration file in the directory 10 | # where the inspected file is and continue its way up to the root directory. 11 | # 12 | # See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md 13 | AllCops: 14 | NewCops: enable 15 | 16 | Layout/LineLength: 17 | Exclude: 18 | - "code-scanning-rubocop.gemspec" 19 | 20 | Layout/SpaceAroundMethodCallOperator: 21 | Enabled: true 22 | 23 | Lint/RaiseException: 24 | Enabled: true 25 | Lint/StructNewOverride: 26 | Enabled: true 27 | 28 | Style/HashSyntax: 29 | EnforcedStyle: ruby19 30 | Style/StringLiterals: 31 | EnforcedStyle: double_quotes 32 | Style/ExponentialNotation: 33 | Enabled: true 34 | Style/HashEachMethods: 35 | Enabled: true 36 | Style/HashTransformKeys: 37 | Enabled: true 38 | Style/HashTransformValues: 39 | Enabled: true 40 | Style/ClassAndModuleChildren: 41 | Exclude: 42 | - "test/**/*" 43 | Metrics/MethodLength: 44 | Enabled: false 45 | Metrics/BlockLength: 46 | Enabled: false 47 | Metrics/AbcSize: 48 | Enabled: false 49 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2020-05-05 14:10:36 -0400 using RuboCop version 0.82.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | Style/Documentation: 10 | Exclude: 11 | - "spec/**/*" 12 | - "test/**/*" 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at arthurnn@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [https://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: https://contributor-covenant.org 74 | [version]: https://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7.1 2 | 3 | ARG GITHUB_WORKSPACE 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in code-scanning-rubocop.gemspec 6 | gemspec 7 | 8 | gem "minitest", "~> 5.0" 9 | gem "rake", "~> 12.0" 10 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | code-scanning-rubocop (0.5.0) 5 | rubocop (~> 1.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | ast (2.4.2) 11 | minitest (5.14.0) 12 | parallel (1.21.0) 13 | parser (3.1.0.0) 14 | ast (~> 2.4.1) 15 | rainbow (3.1.1) 16 | rake (12.3.3) 17 | regexp_parser (2.2.0) 18 | rexml (3.2.5) 19 | rubocop (1.24.1) 20 | parallel (~> 1.10) 21 | parser (>= 3.0.0.0) 22 | rainbow (>= 2.2.2, < 4.0) 23 | regexp_parser (>= 1.8, < 3.0) 24 | rexml 25 | rubocop-ast (>= 1.15.1, < 2.0) 26 | ruby-progressbar (~> 1.7) 27 | unicode-display_width (>= 1.4.0, < 3.0) 28 | rubocop-ast (1.15.1) 29 | parser (>= 3.0.1.1) 30 | ruby-progressbar (1.11.0) 31 | unicode-display_width (2.1.0) 32 | 33 | PLATFORMS 34 | ruby 35 | 36 | DEPENDENCIES 37 | code-scanning-rubocop! 38 | minitest (~> 5.0) 39 | rake (~> 12.0) 40 | 41 | BUNDLED WITH 42 | 2.3.4 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Arthur Nogueira Neves 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 | # CodeScanning::Rubocop 2 | 3 | 'code-scanning-rubocop' is a gem to integrate RuboCop and the GitHub's code scanning feature. 4 | The repository is composed by two components. The gem which can be installed in any ruby application and a default GitHub action to ease the usage of it. 5 | 6 | The rubygem adds a SARIF exporter to the rubocop runner. GitHub's code scanning feature accepts a SARIF file with the 'results' (alerts) generated by the tool. 7 | The action, is what will run rubocop with the exporter. Note: you can only run the gem within your application, and have our own action that calls rubocop. See more in the Installation and Usage sections. 8 | 9 | This is how it would look in your Security tab: 10 | ![preview](preview.png) 11 | 12 | ## Action Installation 13 | 14 | The easiest way to install the integration, is this action template bellow. It will install the gem in your app and run it for you within the GitHub's action enviroment. To install the action create a file `.github/workflows/rubocop-analysis.yml` like the following: 15 | 16 | ```yaml 17 | # .github/workflows/rubocop-analysis.yml 18 | name: "RuboCop" 19 | 20 | on: [push] 21 | 22 | jobs: 23 | rubocop: 24 | runs-on: ubuntu-latest 25 | strategy: 26 | fail-fast: false 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v2 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: 2.6 36 | 37 | # This step is not necessary if you add the gem to your Gemfile 38 | - name: Install Code Scanning integration 39 | run: bundle add code-scanning-rubocop --skip-install 40 | 41 | - name: Install dependencies 42 | run: bundle install 43 | 44 | - name: RuboCop run 45 | run: | 46 | bash -c " 47 | bundle exec rubocop --require code_scanning --format CodeScanning::SarifFormatter -o rubocop.sarif 48 | [[ $? -ne 2 ]] 49 | " 50 | 51 | - name: Upload Sarif output 52 | uses: github/codeql-action/upload-sarif@v1 53 | with: 54 | sarif_file: rubocop.sarif 55 | ``` 56 | 57 | ## Gem installation & usage in a custom action 58 | Note: this is not necessary if you use the action above. 59 | 60 | To install the gem add this line to your application's Gemfile: 61 | 62 | ```ruby 63 | gem 'code-scanning-rubocop' 64 | ``` 65 | 66 | Then, in your custom GitHub's action, you need to run rubocop and make sure you give it the SarifFormatter: 67 | ```bash 68 | bundle exec rubocop --require code_scanning --format CodeScanning::SarifFormatter -o rubocop.sarif 69 | ``` 70 | 71 | As a last step, make sure you upload the `rubocop.sarif` file to the code-scan integration. That will create the Code Scanning alerts. 72 | Thus, add this step to your custom rubocop workflow: 73 | ```yaml 74 | - name: Upload Sarif output 75 | uses: github/codeql-action/upload-sarif@v1 76 | with: 77 | sarif_file: rubocop.sarif 78 | ``` 79 | 80 | 81 | ## Contributing 82 | 83 | Bug reports and pull requests are welcome on GitHub at https://github.com/arthurnn/code-scanning-rubocop. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/arthurnn/code-scanning-rubocop/blob/master/CODE_OF_CONDUCT.md). 84 | 85 | 86 | ## License 87 | 88 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 89 | 90 | ## Code of Conduct 91 | 92 | Everyone interacting in the Code::Scanning::Rubocop project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/arthurnn/code-scanning-rubocop/blob/master/CODE_OF_CONDUCT.md). 93 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rake/testtask" 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << "test" 8 | t.libs << "lib" 9 | t.test_files = FileList["test/**/*_test.rb"] 10 | end 11 | 12 | task :generate_rules do 13 | require_relative "lib/code_scanning/rules_generator" 14 | 15 | begin 16 | output_file = "#{Time.now.strftime('%Y%m%d')}.sarif" 17 | puts "Cloning rubocop repository to read manuals" 18 | puts 19 | 20 | sh "git clone git@github.com:rubocop-hq/rubocop.git _tmp" 21 | 22 | gen = QHelpGenerator.new 23 | Dir["_tmp/manual/cops_*.md"].each do |f| 24 | gen.parse_file(f) 25 | end 26 | puts 27 | puts "Writing rules help sarif to '#{output_file}' file" 28 | puts 29 | File.write(output_file, gen.sarif_json) 30 | ensure 31 | sh "rm -rf _tmp" 32 | end 33 | end 34 | 35 | task default: :test 36 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "code_scanning" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require "irb" 15 | IRB.start(__FILE__) 16 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /code-scanning-rubocop.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path("lib", __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | require_relative "lib/code_scanning/rubocop/version" 7 | 8 | Gem::Specification.new do |spec| 9 | spec.name = "code-scanning-rubocop" 10 | spec.version = CodeScanning::Rubocop::VERSION 11 | spec.authors = ["Arthur Neves"] 12 | spec.email = ["arthurnn@gmail.com"] 13 | 14 | spec.summary = "Extra formater to make rubocop compatible with GitHub's code-scanning feature." 15 | spec.description = "This gem adds a SARIF formatter to rubocop, so we can export alerts to code-scanning inside GitHub." 16 | spec.homepage = "https://github.com/arthurnn/code-scanning-rubocop" 17 | spec.license = "MIT" 18 | spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") 19 | 20 | spec.metadata["homepage_uri"] = spec.homepage 21 | spec.metadata["source_code_uri"] = "https://github.com/arthurnn/code-scanning-rubocop" 22 | # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." 23 | 24 | # Specify which files should be added to the gem when it is released. 25 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 26 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 27 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 28 | end 29 | spec.bindir = "exe" 30 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 31 | spec.require_paths = ["lib"] 32 | 33 | spec.add_dependency "rubocop", "~> 1.0" 34 | end 35 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | cd $GITHUB_WORKSPACE 6 | 7 | # Install correct bundler version 8 | gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" 9 | 10 | bundle add code-scanning-rubocop --version 0.2.0 --skip-install 11 | 12 | bundle install 13 | bundle exec rubocop --require code_scanning --format CodeScanning::SarifFormatter -o rubocop.sarif 14 | 15 | if [ ! -f rubocop.sarif ]; then 16 | exit 1 17 | else 18 | exit 0 19 | fi 20 | -------------------------------------------------------------------------------- /lib/code_scanning.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rubocop" 4 | 5 | module CodeScanning 6 | end 7 | 8 | require_relative "code_scanning/rubocop/sarif_formatter" 9 | require_relative "code_scanning/rubocop/version" 10 | -------------------------------------------------------------------------------- /lib/code_scanning/rubocop/rule.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "pathname" 4 | 5 | module CodeScanning 6 | class Rule 7 | def initialize(cop_name, severity = nil) 8 | @cop_name = cop_name 9 | @severity = severity.to_s 10 | @cop = RuboCop::Cop::Cop.registry.find_by_cop_name(cop_name) 11 | end 12 | 13 | def id 14 | @cop_name 15 | end 16 | 17 | def help(format) 18 | case format 19 | when :text 20 | "More info: #{help_uri}" 21 | when :markdown 22 | "[More info](#{help_uri})" 23 | end 24 | end 25 | 26 | def ==(other) 27 | badge.match?(other.badge) 28 | end 29 | alias eql? == 30 | 31 | def badge 32 | @cop.badge 33 | end 34 | 35 | def sarif_severity 36 | cop_severity = @cop.new.send(:find_severity, nil, @severity) 37 | return cop_severity if %w[warning error].include?(cop_severity) 38 | return "note" if %w[refactor convention].include?(cop_severity) 39 | return "error" if cop_severity == "fatal" 40 | 41 | "none" 42 | end 43 | 44 | def help_uri 45 | return @cop.documentation_url if @cop.documentation_url 46 | return nil unless department_uri 47 | 48 | anchor = "#{badge.department}#{badge.cop_name}".downcase.tr("/", "") 49 | "#{department_uri}##{anchor}" 50 | end 51 | 52 | def department_uri 53 | case badge.department 54 | when :Performance 55 | "https://docs.rubocop.org/rubocop-performance/index.html" 56 | when :Packaging 57 | "https://docs.rubocop.org/rubocop-packaging/cops_packaging.html" 58 | when :Rails 59 | "https://docs.rubocop.org/rubocop-rails/cops_rails.html" 60 | when :Minitest 61 | "https://docs.rubocop.org/rubocop-minitest/cops_minitest.html" 62 | when :RSpec 63 | "https://docs.rubocop.org/rubocop-rspec/cops_rspec.html" 64 | when :"RSpec/Rails" 65 | "https://docs.rubocop.org/rubocop-rspec/cops_rspec_rails.html" 66 | when :"RSpec/Capybara" 67 | "https://docs.rubocop.org/rubocop-rspec/cops_rspec_capybara.html" 68 | when :"RSpec/FactoryBot" 69 | "https://docs.rubocop.org/rubocop-rspec/cops_rspec_factorybot.html" 70 | else 71 | STDERR.puts "WARNING: Unknown docs URI for department #{badge.department}" 72 | nil 73 | end 74 | end 75 | 76 | def to_json(opts = {}) 77 | to_h.to_json(opts) 78 | end 79 | 80 | def cop_config 81 | @config ||= RuboCop::ConfigStore.new.for(Pathname.new(Dir.pwd)) 82 | @cop_config ||= @config.for_cop(@cop.department.to_s) 83 | .merge(@config.for_cop(@cop)) 84 | end 85 | 86 | def to_h 87 | properties = { 88 | "precision" => "very-high" 89 | } 90 | 91 | h = { 92 | "id" => @cop_name, 93 | "name" => @cop_name.tr("/", "").gsub("RSpec", "Rspec"), 94 | "defaultConfiguration" => { 95 | "level" => sarif_severity 96 | }, 97 | "properties" => properties 98 | } 99 | 100 | desc = cop_config["Description"] 101 | unless desc.nil? 102 | h["shortDescription"] = { "text" => desc } 103 | h["fullDescription"] = { "text" => desc } 104 | properties["description"] = desc 105 | end 106 | 107 | if badge.qualified? 108 | kind = badge.department.to_s 109 | properties["tags"] = [kind.downcase] 110 | end 111 | 112 | if help_uri 113 | properties["queryURI"] = help_uri 114 | 115 | h.merge!( 116 | "helpUri" => help_uri, 117 | "help" => { 118 | "text" => help(:text), 119 | "markdown" => help(:markdown) 120 | } 121 | ) 122 | end 123 | 124 | h 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /lib/code_scanning/rubocop/sarif_formatter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "json" 4 | require_relative "rule" 5 | 6 | module CodeScanning 7 | class SarifFormatter < RuboCop::Formatter::BaseFormatter 8 | def initialize(output, options = {}) 9 | super 10 | @sarif = { 11 | "$schema" => "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", 12 | "version" => "2.1.0" 13 | } 14 | @rules_map = {} 15 | @rules = [] 16 | @results = [] 17 | @sarif["runs"] = [ 18 | { 19 | "tool" => { 20 | "driver" => { 21 | "name" => "RuboCop", 22 | "version" => RuboCop::Version.version, 23 | "informationUri" => "https://rubocop.org", 24 | "rules" => @rules 25 | } 26 | }, 27 | "results" => @results 28 | } 29 | ] 30 | end 31 | 32 | def get_rule(cop_name, severity) 33 | r = @rules_map[cop_name] 34 | if r.nil? 35 | rule = Rule.new(cop_name, severity&.name) 36 | r = @rules_map[cop_name] = [rule, @rules.size] 37 | @rules << rule 38 | end 39 | 40 | r 41 | end 42 | 43 | def file_finished(file, offenses) 44 | relative_path = RuboCop::PathUtil.relative_path(file) 45 | 46 | offenses.each do |o| 47 | rule, rule_index = get_rule(o.cop_name, o.severity) 48 | @results << { 49 | "ruleId" => rule.id, 50 | "ruleIndex" => rule_index, 51 | "message" => { 52 | "text" => o.message 53 | }, 54 | "locations" => [ 55 | { 56 | "physicalLocation" => { 57 | "artifactLocation" => { 58 | "uri" => relative_path, 59 | "uriBaseId" => "%SRCROOT%", 60 | }, 61 | "region" => { 62 | "startLine" => o.line, 63 | "startColumn" => o.real_column, 64 | "endColumn" => o.last_column.zero? ? o.real_column : o.last_column + 1 65 | } 66 | } 67 | } 68 | ] 69 | } 70 | end 71 | end 72 | 73 | def finished(_inspected_files) 74 | output.print(sarif_json) 75 | end 76 | 77 | def sarif_json 78 | JSON.pretty_generate(@sarif) 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/code_scanning/rubocop/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CodeScanning 4 | module Rubocop 5 | VERSION = "0.6.1" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/code_scanning/rules_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../code_scanning" 4 | 5 | class QHelpGenerator 6 | def initialize 7 | @formatter = CodeScanning::SarifFormatter.new(nil) 8 | end 9 | 10 | def parse_file(path_to_file) 11 | file = File.open(path_to_file) 12 | current_rule = nil 13 | file.each_with_index do |line, index| 14 | # title: skip 15 | next if index.zero? 16 | 17 | if line[0..2] == "## " 18 | current_cop = line[3..-2] 19 | current_rule, _index = @formatter.get_rule(current_cop, nil) 20 | next 21 | end 22 | 23 | next if current_rule.nil? 24 | if line == "\n" && current_rule.help_empty? 25 | # Don't start the help text with new lines 26 | next 27 | end 28 | 29 | current_rule.append_help(line) 30 | end 31 | end 32 | 33 | def sarif_json 34 | @formatter.sarif_json 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arthurnn/code-scanning-rubocop/3077502361b66fd7e73b056a917649e40f87eb03/preview.png -------------------------------------------------------------------------------- /rubocop-action/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Rubocop code-scanning' 2 | description: 'RuboCop and code-scanning integration' 3 | runs: 4 | using: 'docker' 5 | image: '../Dockerfile' 6 | -------------------------------------------------------------------------------- /test/code_scanning/rubocop_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class CodeScanning::RubocopTest < Minitest::Test 6 | def test_that_it_has_a_version_number 7 | refute_nil ::CodeScanning::Rubocop::VERSION 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 4 | require "code_scanning" 5 | 6 | require "minitest/autorun" 7 | --------------------------------------------------------------------------------