smart_todo 1.0.2 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4a658f8acb049243c04ca03be1c3cf4eacd84a648955c6bc0625f9c497ffbe5
4
- data.tar.gz: 49258142b68fc748a054bb36a3c207b16bb010709a21e935a28fe4bdb379cf02
3
+ metadata.gz: dc3a4665a0110180692321fbdd788be8df4155de16bf75b2967f4c330a759e72
4
+ data.tar.gz: 414a198aa06035126890b002f6552779c73d9f6f84eeeaa904ba2132daa7d4d9
5
5
  SHA512:
6
- metadata.gz: da4d883a649e9c090eabd1415a778503d272ad36baf505688a126bc37e6b010f78a72e3da27ad0b28706ae015f48c41115e3644b68b3c590f1e69918d1b32b39
7
- data.tar.gz: 7ff862bbc35a507e220dd40aca07b1b7719e5e47270456652c4c4bf39a7cf9634ef0132379bb2f5e52038b9eafa6e5fab22f2f26948f172907577dde573f271a
6
+ metadata.gz: 8e0c57b0849a90cd1be9699d264167e7286d622eb42e02cc7dd13231ab41d76d815a455a65571f3890dda7c79dc0228692b0c72c37c50337826df137d0c0f0df
7
+ data.tar.gz: a62b94332abe655cb418d0238eaa24e86db0e48b109877b8192265a244795624a2188cffeb0be429f0204683394b992ce73a0932efc322b4c3ba589cadcd0af4
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ name: Ruby ${{ matrix.version }}
13
+ strategy:
14
+ matrix:
15
+ version: [2.5, 2.6, 2.7, 3.0]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - name: Set up Ruby ${{ matrix.version }}
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ${{ matrix.version }}
23
+ bundler-cache: true
24
+ - name: Run Tests
25
+ run: |
26
+ bundle exec rake test
27
+
@@ -0,0 +1,22 @@
1
+ name: RuboCop
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+
9
+ steps:
10
+ - uses: actions/checkout@v2
11
+ - name: Set up Ruby 3.0
12
+ uses: ruby/setup-ruby@v1
13
+ with:
14
+ ruby-version: 3.0
15
+ bundler-cache: true
16
+ - name: Install gems
17
+ run: |
18
+ bundle config path vendor/bundle
19
+ bundle config set without 'default development test'
20
+ bundle install --jobs 4 --retry 3
21
+ - name: Run RuboCop
22
+ run: bundle exec rubocop --parallel
data/.rubocop.yml CHANGED
@@ -1,7 +1,7 @@
1
- inherit_from:
2
- - http://shopify.github.io/ruby-style-guide/rubocop.yml
1
+ inherit_gem:
2
+ rubocop-shopify: rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.5
5
+ TargetRubyVersion: 3.0
6
6
  Exclude:
7
7
  - vendor/**/*
data/Gemfile CHANGED
@@ -6,6 +6,6 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
6
 
7
7
  gemspec
8
8
 
9
- group :development do
10
- gem 'rubocop', '~> 0.71'
9
+ group :rubocop do
10
+ gem "rubocop-shopify", require: false
11
11
  end
data/Gemfile.lock CHANGED
@@ -1,36 +1,43 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- smart_todo (1.0.2)
4
+ smart_todo (1.3.1)
5
+ rexml
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
- addressable (2.6.0)
10
- public_suffix (>= 2.0.2, < 4.0)
11
- ast (2.4.0)
12
- crack (0.4.3)
13
- safe_yaml (~> 1.0.0)
14
- hashdiff (0.4.0)
15
- jaro_winkler (1.5.3)
16
- minitest (5.11.3)
17
- parallel (1.17.0)
18
- parser (2.6.3.0)
19
- ast (~> 2.4.0)
20
- public_suffix (3.1.1)
10
+ addressable (2.8.0)
11
+ public_suffix (>= 2.0.2, < 5.0)
12
+ ast (2.4.2)
13
+ crack (0.4.5)
14
+ rexml
15
+ hashdiff (1.0.1)
16
+ minitest (5.14.4)
17
+ parallel (1.21.0)
18
+ parser (3.0.2.0)
19
+ ast (~> 2.4.1)
20
+ public_suffix (4.0.6)
21
21
  rainbow (3.0.0)
22
- rake (10.5.0)
23
- rubocop (0.71.0)
24
- jaro_winkler (~> 1.5.1)
22
+ rake (13.0.6)
23
+ regexp_parser (2.1.1)
24
+ rexml (3.2.5)
25
+ rubocop (1.23.0)
25
26
  parallel (~> 1.10)
26
- parser (>= 2.6)
27
+ parser (>= 3.0.0.0)
27
28
  rainbow (>= 2.2.2, < 4.0)
29
+ regexp_parser (>= 1.8, < 3.0)
30
+ rexml
31
+ rubocop-ast (>= 1.12.0, < 2.0)
28
32
  ruby-progressbar (~> 1.7)
29
- unicode-display_width (>= 1.4.0, < 1.7)
30
- ruby-progressbar (1.10.1)
31
- safe_yaml (1.0.5)
32
- unicode-display_width (1.6.0)
33
- webmock (3.6.0)
33
+ unicode-display_width (>= 1.4.0, < 3.0)
34
+ rubocop-ast (1.13.0)
35
+ parser (>= 3.0.1.1)
36
+ rubocop-shopify (2.3.0)
37
+ rubocop (~> 1.22)
38
+ ruby-progressbar (1.11.0)
39
+ unicode-display_width (2.1.0)
40
+ webmock (3.11.2)
34
41
  addressable (>= 2.3.6)
35
42
  crack (>= 0.3.2)
36
43
  hashdiff (>= 0.4.0, < 2.0.0)
@@ -39,12 +46,12 @@ PLATFORMS
39
46
  ruby
40
47
 
41
48
  DEPENDENCIES
42
- bundler (~> 1.17)
49
+ bundler (>= 1.17)
43
50
  minitest (~> 5.0)
44
- rake (~> 10.0)
45
- rubocop (~> 0.71)
51
+ rake (>= 10.0)
52
+ rubocop-shopify
46
53
  smart_todo!
47
54
  webmock
48
55
 
49
56
  BUNDLED WITH
50
- 1.17.3
57
+ 2.2.25
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <img src="https://user-images.githubusercontent.com/8122246/61341925-b936d180-a848-11e9-95c1-0d2f398c51b1.png?raw=true" width="200">
3
3
  </h3>
4
4
 
5
- [![Build Status](https://travis-ci.com/Shopify/smart_todo.svg?branch=master)](https://travis-ci.com/Shopify/smart_todo)
5
+ [![Build Status](https://github.com/Shopify/smart_todo/workflows/CI/badge.svg)](https://github.com/Shopify/smart_todo/actions?query=workflow%3ACI)
6
6
 
7
7
  _SmartTodo_ is a library designed to assign users on TODO comments written in your codebase and help assignees be reminded when it's time to commit to their TODO.
8
8
 
data/dev.yml CHANGED
@@ -4,8 +4,12 @@ type:
4
4
  - ruby
5
5
 
6
6
  up:
7
- - ruby: 2.5.5
7
+ - ruby: 3.0.2
8
8
  - bundler
9
9
 
10
+ console:
11
+ desc: 'start a console'
12
+ run: bin/console
13
+
10
14
  test:
11
15
  bundle exec rake test
data/exe/smart_todo CHANGED
@@ -3,12 +3,10 @@
3
3
 
4
4
  $LOAD_PATH.unshift("#{__dir__}/../lib")
5
5
 
6
- require 'smart_todo'
6
+ require "smart_todo"
7
7
 
8
- unless ENV['ENABLE_SMART_TODO']
9
- puts 'Not running SmartTodo since the ENABLE_SMART_TODO ENV is not set'
10
-
11
- exit(0)
8
+ if ENV["ENABLE_SMART_TODO"] && !ARGV.include?("--dispatcher")
9
+ ARGV << "--dispatcher" << "slack"
12
10
  end
13
11
 
14
12
  SmartTodo::CLI.new.run
@@ -15,40 +15,46 @@ module SmartTodo
15
15
  def run(args = ARGV)
16
16
  paths = define_options.parse!(args)
17
17
  validate_options!
18
- paths << '.' if paths.empty?
18
+ paths << "." if paths.empty?
19
19
 
20
20
  paths.each do |path|
21
21
  normalize_path(path).each do |file|
22
22
  parse_file(file)
23
23
 
24
- STDOUT.print('.')
24
+ STDOUT.print(".")
25
25
  STDOUT.flush
26
26
  end
27
27
  end
28
28
  end
29
29
 
30
- # @raise [ArgumentError] if the +slack_token+ or the +fallback_channel+
31
- # options are not passed to the command line
30
+ # @raise [ArgumentError] In case an option needed by a dispatcher wasn't provided.
31
+ #
32
32
  # @return [void]
33
33
  def validate_options!
34
- @options[:slack_token] ||= ENV.fetch('SMART_TODO_SLACK_TOKEN') { raise(ArgumentError, 'Missing :slack_token') }
35
-
36
- @options.fetch(:fallback_channel) { raise(ArgumentError, 'Missing :fallback_channel') }
34
+ dispatcher.validate_options!(@options)
37
35
  end
38
36
 
39
37
  # @return [OptionParser] an instance of OptionParser
40
38
  def define_options
41
39
  OptionParser.new do |opts|
42
40
  opts.banner = "Usage: smart_todo [options] file_or_path1 file_or_path2 ..."
43
- opts.on('--slack_token TOKEN') do |token|
41
+ opts.on("--slack_token TOKEN") do |token|
44
42
  @options[:slack_token] = token
45
43
  end
46
- opts.on('--fallback_channel CHANNEL') do |channel|
44
+ opts.on("--fallback_channel CHANNEL") do |channel|
47
45
  @options[:fallback_channel] = channel
48
46
  end
47
+ opts.on("--dispatcher DISPATCHER") do |dispatcher|
48
+ @options[:dispatcher] = dispatcher
49
+ end
49
50
  end
50
51
  end
51
52
 
53
+ # @return [Class] a Dispatchers::Base subclass
54
+ def dispatcher
55
+ @dispatcher ||= Dispatchers::Base.class_for(@options[:dispatcher])
56
+ end
57
+
52
58
  # @param path [String] a path to a file or directory
53
59
  # @return [Array<String>] all the directories the parser should run on
54
60
  def normalize_path(path)
@@ -61,13 +67,13 @@ module SmartTodo
61
67
 
62
68
  # @param file [String] a path to a file
63
69
  def parse_file(file)
64
- Parser::CommentParser.new(File.read(file, encoding: 'UTF-8')).parse.each do |todo_node|
70
+ Parser::CommentParser.new(File.read(file, encoding: "UTF-8")).parse.each do |todo_node|
65
71
  event_message = nil
66
72
  event_met = todo_node.metadata.events.find do |event|
67
73
  event_message = Events.public_send(event.method_name, *event.arguments)
68
74
  end
69
75
 
70
- Dispatcher.new(event_message, todo_node, file, @options).dispatch if event_met
76
+ dispatcher.new(event_message, todo_node, file, @options).dispatch if event_met
71
77
  end
72
78
  end
73
79
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartTodo
4
+ module Dispatchers
5
+ class Base
6
+ # Factory pattern to retrive the right dispatcher class.
7
+ #
8
+ # @param dispatcher [String]
9
+ #
10
+ # @return [Class]
11
+ def self.class_for(dispatcher)
12
+ case dispatcher
13
+ when "slack"
14
+ Slack
15
+ when nil, "output"
16
+ Output
17
+ end
18
+ end
19
+
20
+ # Subclasses should define what options from the CLI they need in order
21
+ # to properly deliver the message. For instance the Slack dispatcher
22
+ # requires an API key.
23
+ #
24
+ # @param _options [Hash]
25
+ #
26
+ # @return void
27
+ def self.validate_options!(_options)
28
+ raise(NotImplemetedError, "subclass responsability")
29
+ end
30
+
31
+ # @param event_message [String] the success message associated
32
+ # a specific event
33
+ # @param todo_node [SmartTodo::Parser::TodoNode]
34
+ # @param file [String] the file containing the TODO
35
+ # @param options [Hash]
36
+ def initialize(event_message, todo_node, file, options)
37
+ @event_message = event_message
38
+ @todo_node = todo_node
39
+ @options = options
40
+ @file = file
41
+ @assignees = @todo_node.metadata.assignees
42
+ end
43
+
44
+ # This method gets called when a TODO reminder is expired and needs to be delivered.
45
+ # Dispatchers should implement this method to deliver the message where they need.
46
+ #
47
+ # @return void
48
+ def dispatch
49
+ raise(NotImplemetedError, "subclass responsability")
50
+ end
51
+
52
+ private
53
+
54
+ # Prepare the content of the message to send to the TODO assignee
55
+ #
56
+ # @param user [Hash] contain information about a user
57
+ # @param assignee [String] original string handle the slack message should be sent
58
+ # @return [String]
59
+ def slack_message(user, assignee)
60
+ header = if user.key?("fallback")
61
+ unexisting_user(assignee)
62
+ else
63
+ existing_user
64
+ end
65
+
66
+ <<~EOM
67
+ #{header}
68
+
69
+ You have an assigned TODO in the `#{@file}` file.
70
+ #{@event_message}
71
+
72
+ Here is the associated comment on your TODO:
73
+
74
+ ```
75
+ #{@todo_node.comment.strip}
76
+ ```
77
+ EOM
78
+ end
79
+
80
+ # Message in case a TODO's assignee doesn't exist in the Slack organization
81
+ #
82
+ # @param user [Hash]
83
+ # @return [String]
84
+ def unexisting_user(assignee)
85
+ "Hello :wave:,\n\n`#{assignee}` had an assigned TODO but this user or channel doesn't exist on Slack anymore."
86
+ end
87
+
88
+ # Hello message for user actually existing in the organization
89
+ def existing_user
90
+ "Hello :wave:,"
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartTodo
4
+ module Dispatchers
5
+ # A simple dispatcher that will output the reminder.
6
+ class Output < Base
7
+ def self.validate_options!(_); end
8
+
9
+ # @return void
10
+ def dispatch
11
+ puts slack_message({}, nil)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartTodo
4
+ module Dispatchers
5
+ # Dispatcher that sends TODO reminders on Slack. Assignees can be either individual
6
+ # (using the associated slack email address) or a channel.
7
+ class Slack < Base
8
+ def self.validate_options!(options)
9
+ options[:slack_token] ||= ENV.fetch("SMART_TODO_SLACK_TOKEN") { raise(ArgumentError, "Missing :slack_token") }
10
+
11
+ options.fetch(:fallback_channel) { raise(ArgumentError, "Missing :fallback_channel") }
12
+ end
13
+
14
+ # Make a Slack API call to dispatch the message to each assignee
15
+ #
16
+ # @raise [SlackClient::Error] in case the Slack API returns an error
17
+ # other than `users_not_found`
18
+ #
19
+ # @return [Array] Slack response for each assignee a message was sent to
20
+ def dispatch
21
+ @assignees.each do |assignee|
22
+ dispatch_one(assignee)
23
+ end
24
+ end
25
+
26
+ # Make a Slack API call to dispatch the message to the user or channel
27
+ #
28
+ # @raise [SlackClient::Error] in case the Slack API returns an error
29
+ # other than `users_not_found`
30
+ #
31
+ # @param [String] the assignee handle string
32
+ # @return [Hash] the Slack response
33
+ def dispatch_one(assignee)
34
+ user = slack_user_or_channel(assignee)
35
+
36
+ client.post_message(user.dig("user", "id"), slack_message(user, assignee))
37
+ rescue SlackClient::Error => error
38
+ if ["users_not_found", "channel_not_found"].include?(error.error_code)
39
+ user = { "user" => { "id" => @options[:fallback_channel] }, "fallback" => true }
40
+ else
41
+ raise(error)
42
+ end
43
+
44
+ client.post_message(user.dig("user", "id"), slack_message(user, assignee))
45
+ end
46
+
47
+ private
48
+
49
+ # Returns a formatted hash containing either the user id of a slack user or
50
+ # the channel the message should be sent to.
51
+ #
52
+ # @return [Hash] a suited hash containing the user ID for a given individual or a slack channel
53
+ def slack_user_or_channel(assignee)
54
+ if assignee.include?("@")
55
+ client.lookup_user_by_email(assignee)
56
+ else
57
+ { "user" => { "id" => assignee } }
58
+ end
59
+ end
60
+
61
+ # @return [SlackClient] an instance of SlackClient
62
+ def client
63
+ @client ||= SlackClient.new(@options[:slack_token])
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'time'
3
+ require "time"
4
4
 
5
5
  module SmartTodo
6
6
  module Events
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem("bundler")
4
+ require "bundler"
5
+
6
+ module SmartTodo
7
+ module Events
8
+ # An event that compare the version of a gem specified in your Gemfile.lock
9
+ # with the expected version specifiers.
10
+ class GemBump
11
+ # @param gem_name [String]
12
+ # @param requirements [Array] a list of version specifiers.
13
+ # The specifiers are the same as the one used in Gemfiles or Gemspecs
14
+ #
15
+ # @example Expecting a specific version
16
+ # GemBump.new('rails', ['6.0'])
17
+ #
18
+ # @example Expecting a version in the 5.x.x series
19
+ # GemBump.new('rails', ['> 5.2', '< 6'])
20
+ def initialize(gem_name, requirements)
21
+ @gem_name = gem_name
22
+ @requirements = Gem::Requirement.new(requirements)
23
+ end
24
+
25
+ # @return [String, false]
26
+ def met?
27
+ return error_message if spec_set[@gem_name].empty?
28
+
29
+ installed_version = spec_set[@gem_name].first.version
30
+ if @requirements.satisfied_by?(installed_version)
31
+ message(installed_version)
32
+ else
33
+ false
34
+ end
35
+ end
36
+
37
+ # Error message send to Slack in case a gem couldn't be found
38
+ #
39
+ # @return [String]
40
+ def error_message
41
+ "The gem *#{@gem_name}* is not in your dependencies, I can't determine if your TODO is ready to be addressed."
42
+ end
43
+
44
+ # @return [String]
45
+ def message(version_number)
46
+ "The gem *#{@gem_name}* was updated to version *#{version_number}* and your TODO is now ready to be addressed."
47
+ end
48
+
49
+ private
50
+
51
+ # @return [Bundler::SpecSet] an instance of Bundler::SpecSet
52
+ def spec_set
53
+ @spec_set ||= Bundler.load.specs
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
3
+ require "net/http"
4
+ require "json"
5
5
 
6
6
  module SmartTodo
7
7
  module Events
@@ -30,7 +30,7 @@ module SmartTodo
30
30
  if response.code_type < Net::HTTPClientError
31
31
  error_message
32
32
  elsif (gem = version_released?(response.body))
33
- message(gem['number'])
33
+ message(gem["number"])
34
34
  else
35
35
  false
36
36
  end
@@ -54,13 +54,13 @@ module SmartTodo
54
54
  # @return [true, false]
55
55
  def version_released?(gem_versions)
56
56
  JSON.parse(gem_versions).find do |gem|
57
- @requirements.satisfied_by?(Gem::Version.new(gem['number']))
57
+ @requirements.satisfied_by?(Gem::Version.new(gem["number"]))
58
58
  end
59
59
  end
60
60
 
61
61
  # @return [Net::HTTP] an instance of Net::HTTP
62
62
  def client
63
- @client ||= Net::HTTP.new('rubygems.org', Net::HTTP.https_default_port).tap do |client|
63
+ @client ||= Net::HTTP.new("rubygems.org", Net::HTTP.https_default_port).tap do |client|
64
64
  client.use_ssl = true
65
65
  end
66
66
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
3
+ require "net/http"
4
+ require "json"
5
5
 
6
6
  module SmartTodo
7
7
  module Events
@@ -12,7 +12,7 @@ module SmartTodo
12
12
  # with the `repos` scope in the +SMART_TODO_GITHUB_TOKEN+ environment variable
13
13
  # is required.
14
14
  class IssueClose
15
- TOKEN_ENV = 'SMART_TODO_GITHUB_TOKEN'
15
+ TOKEN_ENV = "SMART_TODO_GITHUB_TOKEN"
16
16
 
17
17
  # @param organization [String]
18
18
  # @param repo [String]
@@ -62,7 +62,7 @@ module SmartTodo
62
62
 
63
63
  # @return [Net::HTTP] an instance of Net::HTTP
64
64
  def client
65
- @client ||= Net::HTTP.new('api.github.com', Net::HTTP.https_default_port).tap do |client|
65
+ @client ||= Net::HTTP.new("api.github.com", Net::HTTP.https_default_port).tap do |client|
66
66
  client.use_ssl = true
67
67
  end
68
68
  end
@@ -72,13 +72,13 @@ module SmartTodo
72
72
  #
73
73
  # @return [true, false]
74
74
  def pull_request_closed?(pull_request)
75
- JSON.parse(pull_request)['state'] == 'closed'
75
+ JSON.parse(pull_request)["state"] == "closed"
76
76
  end
77
77
 
78
78
  # @return [Hash]
79
79
  def default_headers
80
- { 'Accept' => 'application/vnd.github.v3+json' }.tap do |headers|
81
- headers['Authorization'] = "token #{ENV[TOKEN_ENV]}" if ENV[TOKEN_ENV]
80
+ { "Accept" => "application/vnd.github.v3+json" }.tap do |headers|
81
+ headers["Authorization"] = "token #{ENV[TOKEN_ENV]}" if ENV[TOKEN_ENV]
82
82
  end
83
83
  end
84
84
  end
@@ -38,6 +38,15 @@ module SmartTodo
38
38
  GemRelease.new(gem_name, requirements).met?
39
39
  end
40
40
 
41
+ # Check if +gem_name+ was bumped to the +requirements+ expected
42
+ #
43
+ # @param gem_name [String]
44
+ # @param requirements [Array<String>] a list of version specifiers
45
+ # @return [false, String]
46
+ def gem_bump(gem_name, *requirements)
47
+ GemBump.new(gem_name, requirements).met?
48
+ end
49
+
41
50
  # Check if the issue +issue_number+ is closed
42
51
  #
43
52
  # @param organization [String] the GitHub organization name
@@ -45,7 +54,7 @@ module SmartTodo
45
54
  # @param issue_number [String, Integer]
46
55
  # @return [false, String]
47
56
  def issue_close(organization, repo, issue_number)
48
- IssueClose.new(organization, repo, issue_number, type: 'issues').met?
57
+ IssueClose.new(organization, repo, issue_number, type: "issues").met?
49
58
  end
50
59
 
51
60
  # Check if the pull request +pr_number+ is closed
@@ -55,7 +64,7 @@ module SmartTodo
55
64
  # @param pr_number [String, Integer]
56
65
  # @return [false, String]
57
66
  def pull_request_close(organization, repo, pr_number)
58
- IssueClose.new(organization, repo, pr_number, type: 'pulls').met?
67
+ IssueClose.new(organization, repo, pr_number, type: "pulls").met?
59
68
  end
60
69
  end
61
70
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ripper'
3
+ require "ripper"
4
4
 
5
5
  module SmartTodo
6
6
  module Parser