smart_todo 1.9.2 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +1 -1
- data/.github/workflows/rubocop.yml +1 -1
- data/Gemfile.lock +10 -10
- data/lib/smart_todo/cli.rb +32 -1
- data/lib/smart_todo/comment_parser.rb +4 -1
- data/lib/smart_todo/dispatchers/base.rb +10 -1
- data/lib/smart_todo/dispatchers/slack.rb +2 -1
- data/lib/smart_todo/events.rb +32 -0
- data/lib/smart_todo/todo.rb +24 -0
- data/lib/smart_todo/version.rb +1 -1
- data/lib/smart_todo_comment_format_cop.rb +96 -0
- data/lib/smart_todo_cop.rb +95 -9
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd76c16d18b12ed3caada5a661031dd5f14c60aa1b0b227c0eb720116755c2cc
|
|
4
|
+
data.tar.gz: 91dadbb687dcfd22c4152f2b38054873f6d64fce3b43d8d96b8ac6891947e92a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cede44f989ddbc2a4174c5c273e1e7e08521bbaaddc2a18fe5afc88d04fb84cdb6644e9d6c6839f6e7257dc1271f7f188bbdc6edec21a19debb8e0a438c20130
|
|
7
|
+
data.tar.gz: fc3d770921d2c2d7a275236ea068c9aef5f2202de990c644afeb4cfb974581e62e8ba77ae91bc6d6af265b438dde88b62052a9046f57301808a1a695d12e3c1c
|
data/.github/workflows/build.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
smart_todo (1.
|
|
4
|
+
smart_todo (1.11.0)
|
|
5
5
|
prism (~> 1.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -10,26 +10,26 @@ GEM
|
|
|
10
10
|
addressable (2.8.7)
|
|
11
11
|
public_suffix (>= 2.0.2, < 7.0)
|
|
12
12
|
ast (2.4.3)
|
|
13
|
-
bigdecimal (3.1
|
|
14
|
-
crack (1.0.
|
|
13
|
+
bigdecimal (3.3.1)
|
|
14
|
+
crack (1.0.1)
|
|
15
15
|
bigdecimal
|
|
16
16
|
rexml
|
|
17
|
-
hashdiff (1.1
|
|
17
|
+
hashdiff (1.2.1)
|
|
18
18
|
json (2.10.2)
|
|
19
19
|
language_server-protocol (3.17.0.4)
|
|
20
20
|
lint_roller (1.1.0)
|
|
21
|
-
minitest (5.
|
|
21
|
+
minitest (5.26.0)
|
|
22
22
|
parallel (1.26.3)
|
|
23
23
|
parser (3.3.7.3)
|
|
24
24
|
ast (~> 2.4.1)
|
|
25
25
|
racc
|
|
26
|
-
prism (1.
|
|
27
|
-
public_suffix (6.0.
|
|
26
|
+
prism (1.6.0)
|
|
27
|
+
public_suffix (6.0.2)
|
|
28
28
|
racc (1.8.1)
|
|
29
29
|
rainbow (3.1.1)
|
|
30
|
-
rake (13.
|
|
30
|
+
rake (13.3.0)
|
|
31
31
|
regexp_parser (2.10.0)
|
|
32
|
-
rexml (3.4.
|
|
32
|
+
rexml (3.4.4)
|
|
33
33
|
rubocop (1.74.0)
|
|
34
34
|
json (~> 2.3)
|
|
35
35
|
language_server-protocol (~> 3.17.0.2)
|
|
@@ -49,7 +49,7 @@ GEM
|
|
|
49
49
|
unicode-display_width (3.1.4)
|
|
50
50
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
|
51
51
|
unicode-emoji (4.0.4)
|
|
52
|
-
webmock (3.
|
|
52
|
+
webmock (3.26.0)
|
|
53
53
|
addressable (>= 2.8.0)
|
|
54
54
|
crack (>= 0.3.2)
|
|
55
55
|
hashdiff (>= 0.4.0, < 2.0.0)
|
data/lib/smart_todo/cli.rb
CHANGED
|
@@ -66,6 +66,9 @@ module SmartTodo
|
|
|
66
66
|
opts.on("--dispatcher DISPATCHER") do |dispatcher|
|
|
67
67
|
@options[:dispatcher] = dispatcher
|
|
68
68
|
end
|
|
69
|
+
opts.on("--repo [REPO]", "Repository name to include in notifications") do |repo|
|
|
70
|
+
@options[:repo] = repo || File.basename(Dir.pwd)
|
|
71
|
+
end
|
|
69
72
|
end
|
|
70
73
|
end
|
|
71
74
|
|
|
@@ -103,12 +106,40 @@ module SmartTodo
|
|
|
103
106
|
end
|
|
104
107
|
|
|
105
108
|
@errors.concat(todo.errors)
|
|
106
|
-
|
|
109
|
+
|
|
110
|
+
next unless event_met
|
|
111
|
+
|
|
112
|
+
event_message = append_context_if_applicable(event_message, todo, event_met, events)
|
|
113
|
+
|
|
114
|
+
dispatches << [event_message, todo]
|
|
107
115
|
end
|
|
108
116
|
|
|
109
117
|
dispatches
|
|
110
118
|
end
|
|
111
119
|
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# @param event_message [String] the original event message
|
|
123
|
+
# @param todo [Todo] the todo object that may contain context
|
|
124
|
+
# @param event [Event] the event that was met
|
|
125
|
+
# @param events [Events] the events instance for fetching issue context
|
|
126
|
+
# @return [String] the event message, potentially with context appended
|
|
127
|
+
def append_context_if_applicable(event_message, todo, event, events)
|
|
128
|
+
return event_message unless should_apply_context?(todo, event)
|
|
129
|
+
|
|
130
|
+
org, repo, issue_number = todo.context.arguments
|
|
131
|
+
context_message = events.issue_context(org, repo, issue_number)
|
|
132
|
+
|
|
133
|
+
context_message ? "#{event_message}\n\n#{context_message}" : event_message
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @param todo [Todo] the todo object to check for context
|
|
137
|
+
# @param event [Event] the event to check
|
|
138
|
+
# @return [Boolean] true if context should be applied, false otherwise
|
|
139
|
+
def should_apply_context?(todo, event)
|
|
140
|
+
!!todo.context
|
|
141
|
+
end
|
|
142
|
+
|
|
112
143
|
def process_dispatches(dispatches)
|
|
113
144
|
queue = Queue.new
|
|
114
145
|
dispatches.each { |dispatch| queue << dispatch }
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module SmartTodo
|
|
4
4
|
class CommentParser
|
|
5
|
+
SUPPORTED_TAGS = ["TODO", "FIXME", "OPTIMIZE"].freeze
|
|
6
|
+
TAG_PATTERN = /^#\s(#{SUPPORTED_TAGS.join("|")})\(/
|
|
7
|
+
|
|
5
8
|
attr_reader :todos
|
|
6
9
|
|
|
7
10
|
def initialize
|
|
@@ -54,7 +57,7 @@ module SmartTodo
|
|
|
54
57
|
|
|
55
58
|
source = comment.location.slice
|
|
56
59
|
|
|
57
|
-
if source.match?(
|
|
60
|
+
if source.match?(TAG_PATTERN)
|
|
58
61
|
todos << current_todo if current_todo
|
|
59
62
|
current_todo = Todo.new(source, filepath)
|
|
60
63
|
elsif current_todo && (indent = source[/^#(\s*)/, 1].length) && (indent - current_todo.indent == 2)
|
|
@@ -68,7 +68,7 @@ module SmartTodo
|
|
|
68
68
|
<<~EOM
|
|
69
69
|
#{header}
|
|
70
70
|
|
|
71
|
-
You have an assigned TODO in the `#{@file}` file.
|
|
71
|
+
You have an assigned TODO in the `#{@file}` file#{repo}.
|
|
72
72
|
#{@event_message}
|
|
73
73
|
|
|
74
74
|
Here is the associated comment on your TODO:
|
|
@@ -91,6 +91,15 @@ module SmartTodo
|
|
|
91
91
|
def existing_user
|
|
92
92
|
"Hello :wave:,"
|
|
93
93
|
end
|
|
94
|
+
|
|
95
|
+
def repo
|
|
96
|
+
repo = @options[:repo]
|
|
97
|
+
return unless repo
|
|
98
|
+
|
|
99
|
+
unless repo.empty?
|
|
100
|
+
" in repository `#{repo}`"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
94
103
|
end
|
|
95
104
|
end
|
|
96
105
|
end
|
|
@@ -7,7 +7,8 @@ module SmartTodo
|
|
|
7
7
|
class Slack < Base
|
|
8
8
|
class << self
|
|
9
9
|
def validate_options!(options)
|
|
10
|
-
options[:slack_token] ||= ENV.fetch("SMART_TODO_SLACK_TOKEN"
|
|
10
|
+
options[:slack_token] ||= ENV.fetch("SMART_TODO_SLACK_TOKEN", "")
|
|
11
|
+
raise(ArgumentError, "Missing :slack_token") if options[:slack_token].empty?
|
|
11
12
|
|
|
12
13
|
options.fetch(:fallback_channel) { raise(ArgumentError, "Missing :fallback_channel") }
|
|
13
14
|
end
|
data/lib/smart_todo/events.rb
CHANGED
|
@@ -75,6 +75,8 @@ module SmartTodo
|
|
|
75
75
|
false
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
|
+
rescue Net::HTTPError, JSON::ParserError
|
|
79
|
+
"Error retrieving gem information for *#{gem_name}*."
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
# Check if +gem_name+ was bumped to the +requirements+ expected
|
|
@@ -130,6 +132,8 @@ module SmartTodo
|
|
|
130
132
|
else
|
|
131
133
|
false
|
|
132
134
|
end
|
|
135
|
+
rescue Net::HTTPError, JSON::ParserError
|
|
136
|
+
"Error retrieving issue information for #{organization}/#{repo}##{issue_number}."
|
|
133
137
|
end
|
|
134
138
|
|
|
135
139
|
# Check if the pull request +pr_number+ is closed
|
|
@@ -155,6 +159,34 @@ module SmartTodo
|
|
|
155
159
|
else
|
|
156
160
|
false
|
|
157
161
|
end
|
|
162
|
+
rescue Net::HTTPError, JSON::ParserError
|
|
163
|
+
"Error retrieving pull request information for #{organization}/#{repo}##{pr_number}."
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Retrieve context information for an issue
|
|
167
|
+
# This is used when a TODO has a context: issue() attribute
|
|
168
|
+
#
|
|
169
|
+
# @param organization [String] the GitHub organization name
|
|
170
|
+
# @param repo [String] the GitHub repo name
|
|
171
|
+
# @param issue_number [String, Integer]
|
|
172
|
+
# @return [String, nil]
|
|
173
|
+
def issue_context(organization, repo, issue_number)
|
|
174
|
+
headers = github_headers(organization, repo)
|
|
175
|
+
response = github_client.get("/repos/#{organization}/#{repo}/issues/#{issue_number}", headers)
|
|
176
|
+
|
|
177
|
+
if response.code_type < Net::HTTPClientError
|
|
178
|
+
nil
|
|
179
|
+
else
|
|
180
|
+
issue = JSON.parse(response.body)
|
|
181
|
+
state = issue["state"]
|
|
182
|
+
title = issue["title"]
|
|
183
|
+
assignee = issue["assignee"] ? "@#{issue["assignee"]["login"]}" : "unassigned"
|
|
184
|
+
|
|
185
|
+
"📌 Context: Issue ##{issue_number} - \"#{title}\" [#{state}] (#{assignee}) - " \
|
|
186
|
+
"https://github.com/#{organization}/#{repo}/issues/#{issue_number}"
|
|
187
|
+
end
|
|
188
|
+
rescue Net::HTTPError, JSON::ParserError
|
|
189
|
+
nil
|
|
158
190
|
end
|
|
159
191
|
|
|
160
192
|
# Check if the installed ruby version meets requirements.
|
data/lib/smart_todo/todo.rb
CHANGED
|
@@ -4,6 +4,7 @@ module SmartTodo
|
|
|
4
4
|
class Todo
|
|
5
5
|
attr_reader :filepath, :comment, :indent
|
|
6
6
|
attr_reader :events, :assignees, :errors
|
|
7
|
+
attr_accessor :context
|
|
7
8
|
|
|
8
9
|
def initialize(source, filepath = "-e")
|
|
9
10
|
@filepath = filepath
|
|
@@ -12,6 +13,7 @@ module SmartTodo
|
|
|
12
13
|
|
|
13
14
|
@events = []
|
|
14
15
|
@assignees = []
|
|
16
|
+
@context = nil
|
|
15
17
|
@errors = []
|
|
16
18
|
|
|
17
19
|
parse(source[(indent + 1)..])
|
|
@@ -66,6 +68,28 @@ module SmartTodo
|
|
|
66
68
|
end
|
|
67
69
|
when :to
|
|
68
70
|
metadata.assignees << visit(element.value)
|
|
71
|
+
when :context
|
|
72
|
+
value = visit(element.value)
|
|
73
|
+
|
|
74
|
+
unless value.is_a?(String)
|
|
75
|
+
metadata.errors << "Incorrect `:context` format: expected string value"
|
|
76
|
+
next
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
unless value =~ %r{^([^/]+)/([^#]+)#(\d+)$}
|
|
80
|
+
metadata.errors << "Incorrect `:context` format: expected \"org/repo#issue_number\""
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
org = ::Regexp.last_match(1)
|
|
85
|
+
repo = ::Regexp.last_match(2)
|
|
86
|
+
issue_number = ::Regexp.last_match(3)
|
|
87
|
+
|
|
88
|
+
if org.empty? || repo.empty?
|
|
89
|
+
metadata.errors << "Incorrect `:context` format: org and repo cannot be empty"
|
|
90
|
+
else
|
|
91
|
+
metadata.context = CallNode.new(:issue, [org, repo, issue_number], element.value.location)
|
|
92
|
+
end
|
|
69
93
|
end
|
|
70
94
|
end
|
|
71
95
|
end
|
data/lib/smart_todo/version.rb
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "smart_todo"
|
|
4
|
+
require "parser"
|
|
5
|
+
|
|
6
|
+
module RuboCop
|
|
7
|
+
module Cop
|
|
8
|
+
module SmartTodo
|
|
9
|
+
# A RuboCop cop to enforce proper formatting of SmartTodo comments.
|
|
10
|
+
# SmartTodo comments must have their description on separate lines, indented by 2 spaces.
|
|
11
|
+
#
|
|
12
|
+
# Bad:
|
|
13
|
+
# # TODO(on: date('2024-03-29'), to: 'john@example.com'): Remove this
|
|
14
|
+
# # TODO(on: date('2024-03-29'), to: 'john@example.com') Remove this
|
|
15
|
+
# # TODO(on: date('2024-03-29'), to: 'john@example.com')
|
|
16
|
+
# # Remove this (not indented)
|
|
17
|
+
#
|
|
18
|
+
# Good:
|
|
19
|
+
# # TODO(on: date('2024-03-29'), to: 'john@example.com')
|
|
20
|
+
# # Remove this (indented by 2 extra spaces)
|
|
21
|
+
#
|
|
22
|
+
class SmartTodoCommentFormatCop < Base
|
|
23
|
+
extend AutoCorrector
|
|
24
|
+
|
|
25
|
+
MSG_INLINE = "SmartTodo comment must not be on the same line as the TODO. " \
|
|
26
|
+
"For more info please look at https://github.com/Shopify/smart_todo/wiki/Syntax"
|
|
27
|
+
MSG_INDENT = "SmartTodo continuation line must be indented by 2 spaces. " \
|
|
28
|
+
"For more info please look at https://github.com/Shopify/smart_todo/wiki/Syntax"
|
|
29
|
+
|
|
30
|
+
SMART_TODO_PATTERN = /\A\s*#\s*(TODO|FIXME|OPTIMIZE)\(on:/
|
|
31
|
+
INLINE_TEXT_PATTERN = /\A(\s*#\s*(?:TODO|FIXME|OPTIMIZE)\(.+\))\s*:?\s+(.+)/
|
|
32
|
+
|
|
33
|
+
def on_new_investigation
|
|
34
|
+
processed_source.comments.each_with_index do |comment, index|
|
|
35
|
+
next unless smart_todo_comment?(comment)
|
|
36
|
+
|
|
37
|
+
check_inline_text(comment)
|
|
38
|
+
check_continuation_indent(comment, processed_source.comments[index + 1])
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def smart_todo_comment?(comment)
|
|
45
|
+
comment.text.match?(SMART_TODO_PATTERN)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def check_inline_text(comment)
|
|
49
|
+
match = comment.text.match(INLINE_TEXT_PATTERN)
|
|
50
|
+
return unless match
|
|
51
|
+
|
|
52
|
+
add_offense(comment.location.expression, message: MSG_INLINE) do |corrector|
|
|
53
|
+
todo_part = match[1]
|
|
54
|
+
text_part = match[2]
|
|
55
|
+
indentation = " " * comment.location.column
|
|
56
|
+
|
|
57
|
+
corrected = "#{todo_part}\n#{indentation}# #{text_part}"
|
|
58
|
+
corrector.replace(comment.location.expression, corrected)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def check_continuation_indent(comment, next_comment)
|
|
63
|
+
return unless next_comment
|
|
64
|
+
return unless next_comment.location.line == comment.location.line + 1
|
|
65
|
+
return if smart_todo_comment?(next_comment)
|
|
66
|
+
return if empty_comment?(next_comment)
|
|
67
|
+
return if properly_indented?(next_comment)
|
|
68
|
+
|
|
69
|
+
add_offense(next_comment.location.expression, message: MSG_INDENT) do |corrector|
|
|
70
|
+
corrected = fix_indentation(next_comment.text)
|
|
71
|
+
corrector.replace(next_comment.location.expression, corrected)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def empty_comment?(comment)
|
|
76
|
+
comment.text.match?(/\A\s*#\s*\z/)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def properly_indented?(comment)
|
|
80
|
+
# A properly indented continuation has exactly 2 spaces after the #
|
|
81
|
+
comment.text.match?(/\A\s*# \S/)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def fix_indentation(text)
|
|
85
|
+
# Extract leading whitespace and content
|
|
86
|
+
match = text.match(/\A(\s*)#\s*(\S.*)\z/)
|
|
87
|
+
return text unless match
|
|
88
|
+
|
|
89
|
+
leading_space = match[1]
|
|
90
|
+
content = match[2]
|
|
91
|
+
"#{leading_space}# #{content}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/smart_todo_cop.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "smart_todo"
|
|
4
|
+
require "date"
|
|
4
5
|
|
|
5
6
|
module RuboCop
|
|
6
7
|
module Cop
|
|
@@ -12,12 +13,20 @@ module RuboCop
|
|
|
12
13
|
class SmartTodoCop < Base
|
|
13
14
|
HELP = "For more info please look at https://github.com/Shopify/smart_todo/wiki/Syntax"
|
|
14
15
|
MSG = "Don't write regular TODO comments. Write SmartTodo compatible syntax comments. #{HELP}"
|
|
16
|
+
INVESTIGATED_TAGS = ::SmartTodo::CommentParser::SUPPORTED_TAGS +
|
|
17
|
+
::SmartTodo::CommentParser::SUPPORTED_TAGS.map(&:downcase)
|
|
18
|
+
TODO_PATTERN = /^#\s@?(#{INVESTIGATED_TAGS.join("|")})\b/
|
|
15
19
|
|
|
16
20
|
# @param processed_source [RuboCop::ProcessedSource]
|
|
17
21
|
# @return [void]
|
|
18
22
|
def on_new_investigation
|
|
19
23
|
processed_source.comments.each do |comment|
|
|
20
|
-
next unless
|
|
24
|
+
next unless (match = TODO_PATTERN.match(comment.text))
|
|
25
|
+
|
|
26
|
+
if match[1] != match[1].upcase
|
|
27
|
+
add_offense(comment)
|
|
28
|
+
next
|
|
29
|
+
end
|
|
21
30
|
|
|
22
31
|
metadata = metadata(comment.text)
|
|
23
32
|
|
|
@@ -25,10 +34,10 @@ module RuboCop
|
|
|
25
34
|
add_offense(comment, message: "Invalid TODO format: #{metadata.errors.join(", ")}. #{HELP}")
|
|
26
35
|
elsif !smart_todo?(metadata)
|
|
27
36
|
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
37
|
elsif invalid_assignees(metadata.assignees).any?
|
|
31
38
|
add_offense(comment, message: "Invalid event assignee. This method only accepts strings. #{HELP}")
|
|
39
|
+
elsif (invalid_events = validate_events(metadata.events)).any?
|
|
40
|
+
add_offense(comment, message: "#{invalid_events.join(". ")}. #{HELP}")
|
|
32
41
|
end
|
|
33
42
|
end
|
|
34
43
|
end
|
|
@@ -49,17 +58,94 @@ module RuboCop
|
|
|
49
58
|
metadata.assignees.any?
|
|
50
59
|
end
|
|
51
60
|
|
|
52
|
-
# @param metadata [Array<SmartTodo::Parser::MethodNode>]
|
|
53
|
-
# @return [Array<String>]
|
|
54
|
-
def invalid_event_methods(events)
|
|
55
|
-
events.map(&:method_name).reject { |method| ::SmartTodo::Events.method_defined?(method) }
|
|
56
|
-
end
|
|
57
|
-
|
|
58
61
|
# @param assignees [Array]
|
|
59
62
|
# @return [Array]
|
|
60
63
|
def invalid_assignees(assignees)
|
|
61
64
|
assignees.reject { |assignee| assignee.is_a?(String) }
|
|
62
65
|
end
|
|
66
|
+
|
|
67
|
+
# @param events [Array<SmartTodo::Todo::CallNode>]
|
|
68
|
+
# @return [Array<String>]
|
|
69
|
+
def validate_events(events)
|
|
70
|
+
invalid_methods = events.map(&:method_name).reject { |method| ::SmartTodo::Events.method_defined?(method) }
|
|
71
|
+
return ["Invalid event method(s): #{invalid_methods.join(", ")}"] if invalid_methods.any?
|
|
72
|
+
|
|
73
|
+
events.map do |event|
|
|
74
|
+
send(validate_method(event.method_name), event.arguments)
|
|
75
|
+
end.compact
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param event_type [Symbol]
|
|
79
|
+
# @return [String]
|
|
80
|
+
def validate_method(event_type)
|
|
81
|
+
"validate_#{event_type}_args"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @param args [Array]
|
|
85
|
+
# @return [String, nil] Returns error message if date is invalid, nil if valid
|
|
86
|
+
def validate_date_args(args)
|
|
87
|
+
date = args.first
|
|
88
|
+
Date.parse(date)
|
|
89
|
+
nil
|
|
90
|
+
rescue ArgumentError, TypeError
|
|
91
|
+
"Invalid date format: #{date}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @param args [Array]
|
|
95
|
+
# @return [String, nil] Returns error message if arguments are invalid, nil if valid
|
|
96
|
+
def validate_issue_close_args(args)
|
|
97
|
+
validate_fixed_arity_args(args, 3, "issue_close", ["organization", "repo", "issue_number"])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @param args [Array]
|
|
101
|
+
# @return [String, nil] Returns error message if arguments are invalid, nil if valid
|
|
102
|
+
def validate_pull_request_close_args(args)
|
|
103
|
+
validate_fixed_arity_args(args, 3, "pull_request_close", ["organization", "repo", "pr_number"])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @param args [Array]
|
|
107
|
+
# @return [String, nil] Returns error message if arguments are invalid, nil if valid
|
|
108
|
+
def validate_gem_release_args(args)
|
|
109
|
+
validate_gem_args(args, "gem_release")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @param args [Array]
|
|
113
|
+
# @return [String, nil] Returns error message if arguments are invalid, nil if valid
|
|
114
|
+
def validate_gem_bump_args(args)
|
|
115
|
+
validate_gem_args(args, "gem_bump")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @param args [Array]
|
|
119
|
+
# @return [String, nil] Returns error message if arguments are invalid, nil if valid
|
|
120
|
+
def validate_ruby_version_args(args)
|
|
121
|
+
if args.empty?
|
|
122
|
+
"Invalid ruby_version event: Expected at least 1 argument (version requirement), got 0"
|
|
123
|
+
elsif !args.all? { |arg| arg.is_a?(String) }
|
|
124
|
+
"Invalid ruby_version event: Version requirements must be strings"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Helper method for validating fixed arity events
|
|
129
|
+
def validate_fixed_arity_args(args, expected_count, event_name, arg_names)
|
|
130
|
+
if args.size != expected_count
|
|
131
|
+
message = "Invalid #{event_name} event: Expected #{expected_count} arguments "
|
|
132
|
+
message += "(#{arg_names.join(", ")}), got #{args.size}"
|
|
133
|
+
message
|
|
134
|
+
elsif !args.all? { |arg| arg.is_a?(String) }
|
|
135
|
+
"Invalid #{event_name} event: Arguments must be strings"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Helper method for validating gem-related events
|
|
140
|
+
def validate_gem_args(args, event_name)
|
|
141
|
+
if args.empty?
|
|
142
|
+
"Invalid #{event_name} event: Expected at least 1 argument (gem_name), got 0"
|
|
143
|
+
elsif !args[0].is_a?(String)
|
|
144
|
+
"Invalid #{event_name} event: First argument (gem_name) must be a string"
|
|
145
|
+
elsif args.size > 1 && !args[1..].all? { |arg| arg.is_a?(String) }
|
|
146
|
+
"Invalid #{event_name} event: Version requirements must be strings"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
63
149
|
end
|
|
64
150
|
end
|
|
65
151
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smart_todo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shopify
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: prism
|
|
@@ -121,6 +121,7 @@ files:
|
|
|
121
121
|
- lib/smart_todo/slack_client.rb
|
|
122
122
|
- lib/smart_todo/todo.rb
|
|
123
123
|
- lib/smart_todo/version.rb
|
|
124
|
+
- lib/smart_todo_comment_format_cop.rb
|
|
124
125
|
- lib/smart_todo_cop.rb
|
|
125
126
|
- service.yml
|
|
126
127
|
- shipit.rubygems.yml
|
|
@@ -147,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
147
148
|
- !ruby/object:Gem::Version
|
|
148
149
|
version: '0'
|
|
149
150
|
requirements: []
|
|
150
|
-
rubygems_version: 3.
|
|
151
|
+
rubygems_version: 3.7.2
|
|
151
152
|
specification_version: 4
|
|
152
153
|
summary: Enhance todo's comments in your codebase.
|
|
153
154
|
test_files: []
|