smart_todo 1.3.1 → 1.5.0

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: dc3a4665a0110180692321fbdd788be8df4155de16bf75b2967f4c330a759e72
4
- data.tar.gz: 414a198aa06035126890b002f6552779c73d9f6f84eeeaa904ba2132daa7d4d9
3
+ metadata.gz: 4daea874da20235bc9b20d315058644bafb73e78b14c6c266f6252f4b346bcf1
4
+ data.tar.gz: feb305070b73a03e2481515a914cca2fcaad264f3871cd65e242bf58ccb3612f
5
5
  SHA512:
6
- metadata.gz: 8e0c57b0849a90cd1be9699d264167e7286d622eb42e02cc7dd13231ab41d76d815a455a65571f3890dda7c79dc0228692b0c72c37c50337826df137d0c0f0df
7
- data.tar.gz: a62b94332abe655cb418d0238eaa24e86db0e48b109877b8192265a244795624a2188cffeb0be429f0204683394b992ce73a0932efc322b4c3ba589cadcd0af4
6
+ metadata.gz: 7fcba36662ce881114aafa3f5bba320d767c3e299a1d9d91f06a505107815d631c008ed87a1e853d1bb2766c1e9bdce756125ccc9c5ed65917264f962da62814
7
+ data.tar.gz: 981c7785a6c5e4d2805d004f6ec1411a953ec9d68005314bc8fe52bf1deb5f6c036a73ea4a08cf3346a120f765971c4fbb0049bdc4ebf97c867bf7e14f7690b8
@@ -0,0 +1,25 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/ruby
3
+ {
4
+ "name": "Ruby",
5
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6
+ "image": "mcr.microsoft.com/devcontainers/ruby:0-3-bullseye",
7
+ "features": {
8
+ "ghcr.io/devcontainers/features/github-cli:1": {}
9
+ },
10
+
11
+ // Features to add to the dev container. More info: https://containers.dev/features.
12
+ // "features": {},
13
+
14
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
15
+ // "forwardPorts": [],
16
+
17
+ // Use 'postCreateCommand' to run commands after the container is created.
18
+ "postCreateCommand": "bundle install",
19
+
20
+ // Configure tool-specific properties.
21
+ // "customizations": {},
22
+
23
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
24
+ // "remoteUser": "root"
25
+ }
@@ -12,7 +12,7 @@ jobs:
12
12
  name: Ruby ${{ matrix.version }}
13
13
  strategy:
14
14
  matrix:
15
- version: [2.5, 2.6, 2.7, 3.0]
15
+ version: [3.0, 3.1, 3.2]
16
16
 
17
17
  steps:
18
18
  - uses: actions/checkout@v2
@@ -24,4 +24,3 @@ jobs:
24
24
  - name: Run Tests
25
25
  run: |
26
26
  bundle exec rake test
27
-
@@ -0,0 +1,22 @@
1
+ name: Contributor License Agreement (CLA)
2
+
3
+ on:
4
+ pull_request_target:
5
+ types: [opened, synchronize]
6
+ issue_comment:
7
+ types: [created]
8
+
9
+ jobs:
10
+ cla:
11
+ runs-on: ubuntu-latest
12
+ if: |
13
+ (github.event.issue.pull_request
14
+ && !github.event.issue.pull_request.merged_at
15
+ && contains(github.event.comment.body, 'signed')
16
+ )
17
+ || (github.event.pull_request && !github.event.pull_request.merged)
18
+ steps:
19
+ - uses: Shopify/shopify-cla-action@v1
20
+ with:
21
+ github-token: ${{ secrets.GITHUB_TOKEN }}
22
+ cla-token: ${{ secrets.CLA_TOKEN }}
data/.rubocop.yml CHANGED
@@ -3,5 +3,7 @@ inherit_gem:
3
3
 
4
4
  AllCops:
5
5
  TargetRubyVersion: 3.0
6
+ NewCops: disable
7
+ SuggestExtensions: false
6
8
  Exclude:
7
9
  - vendor/**/*
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- smart_todo (1.3.1)
4
+ smart_todo (1.5.0)
5
5
  rexml
6
6
 
7
7
  GEM
@@ -13,30 +13,34 @@ GEM
13
13
  crack (0.4.5)
14
14
  rexml
15
15
  hashdiff (1.0.1)
16
+ json (2.6.3)
16
17
  minitest (5.14.4)
17
- parallel (1.21.0)
18
- parser (3.0.2.0)
18
+ parallel (1.23.0)
19
+ parser (3.2.2.3)
19
20
  ast (~> 2.4.1)
21
+ racc
20
22
  public_suffix (4.0.6)
21
- rainbow (3.0.0)
23
+ racc (1.7.0)
24
+ rainbow (3.1.1)
22
25
  rake (13.0.6)
23
- regexp_parser (2.1.1)
26
+ regexp_parser (2.8.1)
24
27
  rexml (3.2.5)
25
- rubocop (1.23.0)
28
+ rubocop (1.52.1)
29
+ json (~> 2.3)
26
30
  parallel (~> 1.10)
27
- parser (>= 3.0.0.0)
31
+ parser (>= 3.2.2.3)
28
32
  rainbow (>= 2.2.2, < 4.0)
29
33
  regexp_parser (>= 1.8, < 3.0)
30
- rexml
31
- rubocop-ast (>= 1.12.0, < 2.0)
34
+ rexml (>= 3.2.5, < 4.0)
35
+ rubocop-ast (>= 1.28.0, < 2.0)
32
36
  ruby-progressbar (~> 1.7)
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)
37
+ unicode-display_width (>= 2.4.0, < 3.0)
38
+ rubocop-ast (1.29.0)
39
+ parser (>= 3.2.1.0)
40
+ rubocop-shopify (2.14.0)
41
+ rubocop (~> 1.51)
42
+ ruby-progressbar (1.13.0)
43
+ unicode-display_width (2.4.2)
40
44
  webmock (3.11.2)
41
45
  addressable (>= 2.3.6)
42
46
  crack (>= 0.3.2)
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2019 Shopify
3
+ Copyright (c) 2019-present Shopify
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/bin/rubocop CHANGED
@@ -9,8 +9,10 @@
9
9
  #
10
10
 
11
11
  require "pathname"
12
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
- Pathname.new(__FILE__).realpath)
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path(
13
+ "../../Gemfile",
14
+ Pathname.new(__FILE__).realpath,
15
+ )
14
16
 
15
17
  bundle_binstub = File.expand_path("../bundle", __FILE__)
16
18
 
data/exe/smart_todo CHANGED
@@ -9,4 +9,4 @@ if ENV["ENABLE_SMART_TODO"] && !ARGV.include?("--dispatcher")
9
9
  ARGV << "--dispatcher" << "slack"
10
10
  end
11
11
 
12
- SmartTodo::CLI.new.run
12
+ exit SmartTodo::CLI.new.run
@@ -9,21 +9,35 @@ module SmartTodo
9
9
  class CLI
10
10
  def initialize
11
11
  @options = {}
12
+ @errors = []
12
13
  end
13
14
 
14
15
  # @param args [Array<String>]
15
16
  def run(args = ARGV)
16
17
  paths = define_options.parse!(args)
17
18
  validate_options!
19
+
18
20
  paths << "." if paths.empty?
19
21
 
20
22
  paths.each do |path|
21
23
  normalize_path(path).each do |file|
22
24
  parse_file(file)
23
25
 
24
- STDOUT.print(".")
25
- STDOUT.flush
26
+ $stdout.print(".")
27
+ $stdout.flush
28
+ end
29
+ end
30
+
31
+ if @errors.empty?
32
+ 0
33
+ else
34
+ $stderr.puts "There were errors while checking for TODOs:\n"
35
+
36
+ @errors.each do |error|
37
+ $stderr.puts error
26
38
  end
39
+
40
+ 1
27
41
  end
28
42
  end
29
43
 
@@ -71,8 +85,17 @@ module SmartTodo
71
85
  event_message = nil
72
86
  event_met = todo_node.metadata.events.find do |event|
73
87
  event_message = Events.public_send(event.method_name, *event.arguments)
88
+ rescue => e
89
+ message = "Error while parsing #{file} on event `#{event.method_name}` with arguments #{event.arguments}: " \
90
+ "#{e.message}"
91
+
92
+ @errors << message
93
+
94
+ nil
74
95
  end
75
96
 
97
+ @errors.concat(todo_node.metadata.errors)
98
+
76
99
  dispatcher.new(event_message, todo_node, file, @options).dispatch if event_met
77
100
  end
78
101
  end
@@ -3,29 +3,31 @@
3
3
  module SmartTodo
4
4
  module Dispatchers
5
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
6
+ class << self
7
+ # Factory pattern to retrieve the right dispatcher class.
8
+ #
9
+ # @param dispatcher [String]
10
+ #
11
+ # @return [Class]
12
+ def class_for(dispatcher)
13
+ case dispatcher
14
+ when "slack"
15
+ Slack
16
+ when nil, "output"
17
+ Output
18
+ end
17
19
  end
18
- end
19
20
 
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")
21
+ # Subclasses should define what options from the CLI they need in order
22
+ # to properly deliver the message. For instance the Slack dispatcher
23
+ # requires an API key.
24
+ #
25
+ # @param _options [Hash]
26
+ #
27
+ # @return void
28
+ def validate_options!(_options)
29
+ raise(NotImplemetedError, "subclass responsability")
30
+ end
29
31
  end
30
32
 
31
33
  # @param event_message [String] the success message associated
@@ -4,7 +4,9 @@ module SmartTodo
4
4
  module Dispatchers
5
5
  # A simple dispatcher that will output the reminder.
6
6
  class Output < Base
7
- def self.validate_options!(_); end
7
+ class << self
8
+ def validate_options!(_); end
9
+ end
8
10
 
9
11
  # @return void
10
12
  def dispatch
@@ -5,10 +5,12 @@ module SmartTodo
5
5
  # Dispatcher that sends TODO reminders on Slack. Assignees can be either individual
6
6
  # (using the associated slack email address) or a channel.
7
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") }
8
+ class << self
9
+ def validate_options!(options)
10
+ options[:slack_token] ||= ENV.fetch("SMART_TODO_SLACK_TOKEN") { raise(ArgumentError, "Missing :slack_token") }
10
11
 
11
- options.fetch(:fallback_channel) { raise(ArgumentError, "Missing :fallback_channel") }
12
+ options.fetch(:fallback_channel) { raise(ArgumentError, "Missing :fallback_channel") }
13
+ end
12
14
  end
13
15
 
14
16
  # Make a Slack API call to dispatch the message to each assignee
@@ -35,7 +37,7 @@ module SmartTodo
35
37
 
36
38
  client.post_message(user.dig("user", "id"), slack_message(user, assignee))
37
39
  rescue SlackClient::Error => error
38
- if ["users_not_found", "channel_not_found"].include?(error.error_code)
40
+ if ["users_not_found", "channel_not_found", "is_archived"].include?(error.error_code)
39
41
  user = { "user" => { "id" => @options[:fallback_channel] }, "fallback" => true }
40
42
  else
41
43
  raise(error)
@@ -6,20 +6,22 @@ module SmartTodo
6
6
  module Events
7
7
  # An event that check if the passed date is passed
8
8
  class Date
9
- # @param on_date [String] a string parsable by Time.parse
10
- # @return [String, false]
11
- def self.met?(on_date)
12
- if Time.now >= Time.parse(on_date)
13
- message(on_date)
14
- else
15
- false
9
+ class << self
10
+ # @param on_date [String] a string parsable by Time.parse
11
+ # @return [String, false]
12
+ def met?(on_date)
13
+ if Time.now >= Time.parse(on_date)
14
+ message(on_date)
15
+ else
16
+ false
17
+ end
16
18
  end
17
- end
18
19
 
19
- # @param on_date [String]
20
- # @return [String]
21
- def self.message(on_date)
22
- "We are past the *#{on_date}* due date and your TODO is now ready to be addressed."
20
+ # @param on_date [String]
21
+ # @return [String]
22
+ def message(on_date)
23
+ "We are past the *#{on_date}* due date and your TODO is now ready to be addressed."
24
+ end
23
25
  end
24
26
  end
25
27
  end
@@ -18,10 +18,12 @@ module SmartTodo
18
18
 
19
19
  # This class is used to parse the ruby TODO() comment.
20
20
  class MetadataParser < Ripper
21
- # @param source [String] the actual Ruby code
22
- def self.parse(source)
23
- sexp = new(source).parse
24
- Visitor.new.tap { |v| v.process(sexp) }
21
+ class << self
22
+ # @param source [String] the actual Ruby code
23
+ def parse(source)
24
+ sexp = new(source).parse
25
+ Visitor.new.tap { |v| v.process(sexp) }
26
+ end
25
27
  end
26
28
 
27
29
  # @return [Array] an Array of Array
@@ -78,11 +80,12 @@ module SmartTodo
78
80
  end
79
81
 
80
82
  class Visitor
81
- attr_reader :events, :assignees
83
+ attr_reader :events, :assignees, :errors
82
84
 
83
85
  def initialize
84
86
  @events = []
85
87
  @assignees = []
88
+ @errors = []
86
89
  end
87
90
 
88
91
  # Iterate over each tokens returned from the parser and call
@@ -104,9 +107,11 @@ module SmartTodo
104
107
  # @param method_node [MethodNode]
105
108
  # @return [void]
106
109
  def on_todo_event(method_node)
107
- return unless method_node.is_a?(MethodNode)
108
-
109
- events << method_node
110
+ if method_node.is_a?(MethodNode)
111
+ events << method_node
112
+ else
113
+ errors << "Incorrect `:on` event format: #{method_node}"
114
+ end
110
115
  end
111
116
 
112
117
  # @param assignee [String]
@@ -82,7 +82,7 @@ module SmartTodo
82
82
  slack_response!(response)
83
83
  end
84
84
 
85
- # Chech if the response to Slack was a 200 and the Slack API request was successful
85
+ # Check if the response to Slack was a 200 and the Slack API request was successful
86
86
  #
87
87
  # @param response [Net::HTTPResponse] a net Net::HTTPResponse subclass
88
88
  # (Net::HTTPOK, Net::HTTPNotFound ...)
@@ -92,6 +92,7 @@ module SmartTodo
92
92
  # @raise [SlackClient::Error] in case Slack returns a { ok: false } in the body
93
93
  def slack_response!(response)
94
94
  raise(Net::HTTPError.new("Request to slack failed", response)) unless response.code_type < Net::HTTPSuccess
95
+
95
96
  body = JSON.parse(response.body)
96
97
 
97
98
  if body["ok"]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SmartTodo
4
- VERSION = "1.3.1"
4
+ VERSION = "1.5.0"
5
5
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "smart_todo/parser/metadata_parser"
3
+ require "smart_todo"
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -10,29 +10,48 @@ module RuboCop
10
10
  #
11
11
  # @see https://rubocop.readthedocs.io/en/latest/extensions/#loading-extensions
12
12
  class SmartTodoCop < Cop
13
- MSG = "Don't write regular TODO comments. Write SmartTodo compatible syntax comments." \
14
- "For more info please look at https://github.com/shopify/smart_todo"
13
+ HELP = "For more info please look at https://github.com/Shopify/smart_todo/wiki/Syntax"
14
+ MSG = "Don't write regular TODO comments. Write SmartTodo compatible syntax comments. #{HELP}"
15
15
 
16
16
  # @param processed_source [RuboCop::ProcessedSource]
17
17
  # @return [void]
18
18
  def investigate(processed_source)
19
19
  processed_source.comments.each do |comment|
20
20
  next unless /^#\sTODO/ =~ comment.text
21
- next if smart_todo?(comment.text)
22
21
 
23
- add_offense(comment)
22
+ metadata = metadata(comment.text)
23
+
24
+ if metadata.errors.any?
25
+ add_offense(comment, message: "Invalid TODO format: #{metadata.errors.join(", ")}. #{HELP}")
26
+ elsif !smart_todo?(metadata)
27
+ add_offense(comment)
28
+ elsif (methods = invalid_event_methods(metadata.events)).any?
29
+ add_offense(comment, message: "Invalid event method(s): #{methods.join(", ")}. #{HELP}")
30
+ end
24
31
  end
25
32
  end
26
33
 
34
+ private
35
+
27
36
  # @param comment [String]
28
- # @return [true, false]
29
- def smart_todo?(comment)
30
- metadata = ::SmartTodo::Parser::MetadataParser.parse(comment.gsub(/^#/, ""))
37
+ # @return [SmartTodo::Parser::Visitor]
38
+ def metadata(comment)
39
+ ::SmartTodo::Parser::MetadataParser.parse(comment.gsub(/^#/, ""))
40
+ end
31
41
 
42
+ # @param metadata [SmartTodo::Parser::Visitor]
43
+ # @return [true, false]
44
+ def smart_todo?(metadata)
32
45
  metadata.events.any? &&
33
46
  metadata.events.all? { |event| event.is_a?(::SmartTodo::Parser::MethodNode) } &&
34
47
  metadata.assignees.any?
35
48
  end
49
+
50
+ # @param metadata [Array<SmartTodo::Parser::MethodNode>]
51
+ # @return [Array<String>]
52
+ def invalid_event_methods(events)
53
+ events.map(&:method_name).reject { |method| ::SmartTodo::Events.respond_to?(method) }
54
+ end
36
55
  end
37
56
  end
38
57
  end
data/service.yml CHANGED
@@ -1 +0,0 @@
1
- classification: library
data/smart_todo.gemspec CHANGED
@@ -24,6 +24,8 @@ Gem::Specification.new do |spec|
24
24
  spec.metadata["changelog_uri"] = spec.homepage + "/releases"
25
25
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
26
26
 
27
+ spec.required_ruby_version = ">= 3.0.0"
28
+
27
29
  spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
28
30
  %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
29
31
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_todo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-23 00:00:00.000000000 Z
11
+ date: 2023-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rexml
@@ -91,7 +91,9 @@ executables:
91
91
  extensions: []
92
92
  extra_rdoc_files: []
93
93
  files:
94
+ - ".devcontainer/devcontainer.json"
94
95
  - ".github/workflows/build.yml"
96
+ - ".github/workflows/cla.yml"
95
97
  - ".github/workflows/rubocop.yml"
96
98
  - ".gitignore"
97
99
  - ".rubocop.yml"
@@ -141,14 +143,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
143
  requirements:
142
144
  - - ">="
143
145
  - !ruby/object:Gem::Version
144
- version: '0'
146
+ version: 3.0.0
145
147
  required_rubygems_version: !ruby/object:Gem::Requirement
146
148
  requirements:
147
149
  - - ">="
148
150
  - !ruby/object:Gem::Version
149
151
  version: '0'
150
152
  requirements: []
151
- rubygems_version: 3.2.20
153
+ rubygems_version: 3.4.13
152
154
  signing_key:
153
155
  specification_version: 4
154
156
  summary: Enhance todo's comments in your codebase.