Linter for Enforcing Downstream Checks

Major Project Presentation

Utkarsh Gupta

Samyak Jain

(8CSE - 8Y)

Presented By:

(A2305217557)

(A2305217638)

ASET (B.Tech. CSE)

Faculty Guide:

Dr. Pooja Singh

Assistant Professor

(Deptartment of CSE)

ASET, AUUP

About the project

The main goal of this project is to provide a tool to automatically detect those issues and report them upstream.

WHAT IS THE PROJECT? AND WHAT ARE THE GOALS?

During the maintenance of the Ruby packages in Debian, several issues in upstream codebases have been identified that make it difficult to build a Debian package out of Ruby gems.

What is Debian?

Debian is a free operating system (OS) for your computer. An operating system is the set of basic programs and utilities that make your computer run.

Debian provides more than a pure OS: it comes with over 65000 packages, precompiled software bundled up in a nice format for easy installation on your machine.

  • For the problem being faced, the best solution is to write a linter in the Ruby language.

WHAT IS TO BE DONE?

SOLUTION

  • To write a linter (for any language),
    the first thing would be to create
    an
    Abstract Syntax Tree.
  • For a linter to work, the whole source code needs to be traversed and processed in the form of an AST.
 
  • On that AST, we can write and match node patterns to match the problematic lines and report them directly.
 

IMPLEMENTATION

HOW IS IT TO BE DONE?

  class GemspecGit < Cop
 
    def_node_search :xstr, <<~PATTERN
      (block
        (send
          (const
            (const {cbase nil?} :Gem) :Specification) :new)
        (args
          (arg _)) `$(xstr (str start_with('git'))))
    PATTERN

The usage of `git ls-files` in the `gemspec` file can be determined by the following AST:

STEP 1:

IMPLEMENTATION

HOW IS IT TO BE DONE?

  def investigate(processed_source)
    xstr(processed_source.ast).each do |node|
      add_offense(
        processed_source.ast,
        location: node.loc.expression,
        message: MSG
      )
    end
  end

The next thing is to process the AST formed against the source code to match problematic lines:

STEP 2:

 

IMPLEMENTATION

HOW IS IT TO BE DONE?

RSpec.describe RuboCop::Cop::Packaging::GemspecGit do
  subject(:cop) { described_class.new(config) }

  let(:config) { RuboCop::Config.new }
  let(:message) { RuboCop::Cop::Packaging::GemspecGit::MSG }

  it 'registers an offense when using `git` for :files=' do
    expect_offense(<<~RUBY)
      Gem::Specification.new do |spec|
        spec.files = `git ls-files`.split("\\n")
                     ^^^^^^^^^^^^^^ #{message}
      end
    RUBY
  end
end

Write tests \o/

STEP 3:

 

IMPLEMENTATION

HOW IS IT TO BE DONE?

    class RequireRelativeHardcodingLib < Base
      include RuboCop::Packaging::LibHelperModule
      extend AutoCorrector

      def_node_matcher :require_relative, <<~PATTERN
        (send nil? :require_relative
          (str #falls_in_lib?))
      PATTERN

The usage of `require_relative` in the spec/ dir can be determined by the following AST:

STEP 1:

IMPLEMENTATION

HOW IS IT TO BE DONE?

  def on_new_investigation
    @file_path = processed_source.file_path
    @file_directory = File.dirname(@file_path)
  end

  def on_send(node)
    return unless require_relative(node)

    add_offense(node) do |corrector|
      corrector.replace(node, good_require_call)
    end
  end

The next thing is to process the AST formed against the source code to match problematic lines:

STEP 2:

 

IMPLEMENTATION

HOW IS IT TO BE DONE?

RSpec.describe RuboCop::Cop::Packaging::RequireRelativeHardcodingLib, :config do
  let(:message) { RuboCop::Cop::Packaging::RequireRelativeHardcodingLib::MSG }
  let(:project_root) { RuboCop::ConfigLoader.project_root }

  context "when `require_relative` call lies outside spec/" do
    let(:filename) { "#{project_root}/spec/foo_spec.rb" }
    let(:source) { "require_relative '../lib/foo.rb'" }

    it "registers an offense" do
      expect_offense(<<~RUBY, filename)
        #{source}
        #{"^" * source.length} #{message}
      RUBY
    end
  end
end

Write tests \o/

STEP 3:

 

USAGE

HOW TO USE THIS TOOL?

Now, the tool is ready to be deployed, let's use this in other projects:

(this correctly determies the usage of `git ls-files` in the `gemspec` file)

END PRODUCT

WHAT IS THE END OUTCOME
OF THIS PROJECT?

I expect the following outcome from this entire project:

  • The tool should be able to, at least, correctly determine around 5-8 major problems and report it.
  • The tool will be deployed to production and will be available to all to install via `apt install`  or `gem install`.
  • It should be helping Debian (downstream!) and other upstream libraries should be using it on the CI.

Where's the tool used?

Selected for Google Summer of Code - 2020

(https://wiki.debian.org/SummerOfCode2020/Projects)

(https://wiki.debian.org/SummerOfCode2020/Projects)

Being used by hundreds of people, already.

(https://github.com/utkarsh2102/rubocop-packaging)

THANK YOU!

RuboCop :: Packaging

By utkarsh2102

RuboCop :: Packaging

This slide is made for the purpose of my major and minor projects.

  • 641