checkoff 0.256.0 → 0.258.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/checkoff/attachments.rb +12 -11
  3. data/lib/checkoff/cli.rb +11 -14
  4. data/lib/checkoff/clients.rb +2 -2
  5. data/lib/checkoff/custom_fields.rb +9 -7
  6. data/lib/checkoff/internal/asana_event_enrichment.rb +17 -11
  7. data/lib/checkoff/internal/asana_event_filter.rb +4 -0
  8. data/lib/checkoff/internal/config_loader.rb +2 -2
  9. data/lib/checkoff/internal/logging.rb +2 -2
  10. data/lib/checkoff/internal/search_url/custom_field_param_converter.rb +8 -7
  11. data/lib/checkoff/internal/search_url/custom_field_variant.rb +2 -0
  12. data/lib/checkoff/internal/search_url/date_param_converter.rb +8 -2
  13. data/lib/checkoff/internal/search_url/parser.rb +7 -5
  14. data/lib/checkoff/internal/search_url/results_merger.rb +1 -2
  15. data/lib/checkoff/internal/search_url/simple_param_converter.rb +13 -4
  16. data/lib/checkoff/internal/selector_classes/common/function_evaluator.rb +3 -0
  17. data/lib/checkoff/internal/selector_classes/common.rb +14 -30
  18. data/lib/checkoff/internal/selector_classes/function_evaluator.rb +13 -2
  19. data/lib/checkoff/internal/selector_classes/project/function_evaluator.rb +9 -0
  20. data/lib/checkoff/internal/selector_classes/project.rb +6 -13
  21. data/lib/checkoff/internal/selector_classes/section/function_evaluator.rb +6 -3
  22. data/lib/checkoff/internal/selector_classes/section.rb +2 -4
  23. data/lib/checkoff/internal/selector_classes/task.rb +42 -16
  24. data/lib/checkoff/internal/selector_evaluator.rb +4 -6
  25. data/lib/checkoff/internal/task_timing.rb +6 -5
  26. data/lib/checkoff/internal/thread_local.rb +2 -1
  27. data/lib/checkoff/my_tasks.rb +3 -1
  28. data/lib/checkoff/portfolios.rb +1 -1
  29. data/lib/checkoff/projects.rb +4 -3
  30. data/lib/checkoff/sections.rb +7 -11
  31. data/lib/checkoff/subtasks.rb +2 -1
  32. data/lib/checkoff/tags.rb +3 -2
  33. data/lib/checkoff/task_searches.rb +6 -3
  34. data/lib/checkoff/task_selectors.rb +3 -3
  35. data/lib/checkoff/tasks.rb +10 -9
  36. data/lib/checkoff/timelines.rb +21 -5
  37. data/lib/checkoff/timing.rb +4 -8
  38. data/lib/checkoff/version.rb +1 -1
  39. data/lib/checkoff/workspaces.rb +5 -2
  40. data/rbi/checkoff.rbi +145 -104
  41. data/sig/checkoff.rbs +110 -97
  42. metadata +2 -2
@@ -105,12 +105,9 @@ module Checkoff
105
105
  # @param custom_field_name [String]
106
106
  # @return [String, nil]
107
107
  def evaluate(resource, custom_field_name)
108
- # @sg-ignore
109
- custom_field = @custom_fields.resource_custom_field_by_name(resource, custom_field_name)
110
- # @sg-ignore
108
+ custom_field = custom_fields.resource_custom_field_by_name(resource, custom_field_name)
111
109
  return nil if custom_field.nil?
112
110
 
113
- # @sg-ignore
114
111
  custom_field['display_value']
115
112
  end
116
113
  end
@@ -125,17 +122,13 @@ module Checkoff
125
122
  false
126
123
  end
127
124
 
128
- # @sg-ignore
129
125
  # @param resource [Asana::Resources::Task,Asana::Resources::Project]
130
- # @sg-ignore
131
126
  # @param custom_field_gid [String]
132
127
  # @return [String, nil]
133
- # @sg-ignore
134
128
  def evaluate(resource, custom_field_gid)
135
- # @sg-ignore
136
- custom_field = @custom_fields.resource_custom_field_by_gid_or_raise(resource, custom_field_gid)
137
- # @sg-ignore
138
- custom_field['display_value']
129
+ custom_field = custom_fields.resource_custom_field_by_gid_or_raise(resource, custom_field_gid)
130
+ display_value = custom_field['display_value']
131
+ display_value.nil? ? nil : T.cast(display_value, String)
139
132
  end
140
133
  end
141
134
 
@@ -152,17 +145,13 @@ module Checkoff
152
145
  end
153
146
 
154
147
  # @param resource [Asana::Resources::Task,Asana::Resources::Project]
155
- # @sg-ignore
156
148
  # @param custom_field_gid [String]
157
149
  # @param custom_field_values_gids [Array<String>]
158
150
  # @return [Boolean]
159
- # @sg-ignore
160
151
  def evaluate(resource, custom_field_gid, custom_field_values_gids)
161
- # @sg-ignore
162
- actual_custom_field_values_gids = @custom_fields.resource_custom_field_values_gids_or_raise(resource,
163
- custom_field_gid)
152
+ actual_custom_field_values_gids = custom_fields.resource_custom_field_values_gids_or_raise(resource,
153
+ custom_field_gid)
164
154
 
165
- # @sg-ignore
166
155
  actual_custom_field_values_gids.intersect?(custom_field_values_gids)
167
156
  end
168
157
  end
@@ -181,17 +170,13 @@ module Checkoff
181
170
 
182
171
  # @param resource [Asana::Resources::Task,Asana::Resources::Project]
183
172
  # @param custom_field_name [String]
184
- # @sg-ignore
185
173
  # @param custom_field_value_names [Array<String>]
186
- # @sg-ignore
187
174
  # @return [Boolean]
188
175
  def evaluate(resource, custom_field_name, custom_field_value_names)
189
176
  actual_custom_field_values_names =
190
- # @sg-ignore
191
- @custom_fields.resource_custom_field_values_names_by_name(resource,
192
- custom_field_name)
177
+ custom_fields.resource_custom_field_values_names_by_name(resource,
178
+ custom_field_name)
193
179
 
194
- # @sg-ignore
195
180
  actual_custom_field_values_names.intersect?(custom_field_value_names)
196
181
  end
197
182
  end
@@ -214,12 +199,10 @@ module Checkoff
214
199
  # @return [Boolean]
215
200
  def evaluate(resource, custom_field_gid, custom_field_values_gids)
216
201
  actual_custom_field_values_gids =
217
- # @sg-ignore
218
- @custom_fields.resource_custom_field_values_gids_or_raise(resource,
219
- custom_field_gid)
202
+ custom_fields.resource_custom_field_values_gids_or_raise(resource,
203
+ custom_field_gid)
220
204
 
221
205
  custom_field_values_gids.all? do |custom_field_value|
222
- # @sg-ignore
223
206
  actual_custom_field_values_gids.include?(custom_field_value)
224
207
  end
225
208
  end
@@ -238,9 +221,10 @@ module Checkoff
238
221
  end
239
222
 
240
223
  # @param resource [Asana::Resources::Task, Asana::Resources::Project]
241
- # @sg-ignore
242
224
  # @param prefix [String]
243
225
  # @return [boolish]
226
+ # @sg-ignore Checkoff::SelectorClasses::Common::NameStartsWithPFunctionEvaluator#evaluate
227
+ # return type could not be inferred
244
228
  def evaluate(resource, prefix)
245
229
  resource.name&.start_with?(prefix)
246
230
  end
@@ -254,9 +238,9 @@ module Checkoff
254
238
 
255
239
  # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
256
240
  # @return [String]
257
- # @sg-ignore
241
+ # @sg-ignore selector is only String here per matches?'s is_a?(String) check, in another method
258
242
  def evaluate(_resource)
259
- T.cast(selector, String)
243
+ selector
260
244
  end
261
245
  end
262
246
  end
@@ -9,21 +9,32 @@ module Checkoff
9
9
  class FunctionEvaluator
10
10
  include Logging
11
11
 
12
+ # Concrete subclasses each declare their own initialize with their
13
+ # specific keyword args (and set @selector themselves before calling
14
+ # super()); this signature exists only so Solargraph can resolve
15
+ # `evaluator_class.new(selector:, **kwargs)` call sites that hold a
16
+ # `Class<FunctionEvaluator>` reference rather than a concrete subclass -
17
+ # deliberately a no-op so a bare `super()` call from those subclasses
18
+ # can't clobber the @selector they already set.
19
+ # @param selector [Symbol, Array, String, nil]
20
+ # @param _kwargs [Hash]
21
+ def initialize(selector: nil, **_kwargs); end
22
+
12
23
  # @param _index [Integer]
13
24
  def evaluate_arg?(_index)
14
25
  true
15
26
  end
16
27
 
17
28
  # @return [Boolean]
18
- # @sg-ignore
29
+ # @sg-ignore return type could not be inferred — abstract method body is only `raise`
19
30
  def matches?
20
31
  raise 'Override me!'
21
32
  end
22
33
 
23
34
  # @param _task [Asana::Resources::Task]
24
35
  # @param _args [Array<Object>]
25
- # @sg-ignore
26
36
  # @return [Object]
37
+ # @sg-ignore return type could not be inferred — abstract method body is only `raise`
27
38
  def evaluate(_task, *_args)
28
39
  raise 'Implement me!'
29
40
  end
@@ -25,6 +25,15 @@ module Checkoff
25
25
  super()
26
26
  end
27
27
 
28
+ # @return [Checkoff::Projects]
29
+ attr_reader :projects
30
+
31
+ # @return [Checkoff::Portfolios]
32
+ attr_reader :portfolios
33
+
34
+ # @return [Checkoff::Workspaces]
35
+ attr_reader :workspaces
36
+
28
37
  private
29
38
 
30
39
  # @return [Array(Symbol, Array)]
@@ -17,7 +17,7 @@ module Checkoff
17
17
 
18
18
  # @param resource [Asana::Resources::Project]
19
19
  # @return [String, nil]
20
- # @sg-ignore
20
+ # @sg-ignore nilable return from an @!parse-declared method isn't inferred
21
21
  def evaluate(resource)
22
22
  resource.due_date
23
23
  end
@@ -33,11 +33,9 @@ module Checkoff
33
33
 
34
34
  # @param project [Asana::Resources::Project]
35
35
  # @param period [Symbol] - :now_or_before or :this_week
36
- # @sg-ignore
37
36
  # @return [Boolean]
38
37
  def evaluate(project, period = :now_or_before)
39
- # @sg-ignore
40
- @projects.project_ready?(project, period:)
38
+ projects.project_ready?(project, period:)
41
39
  end
42
40
  end
43
41
 
@@ -53,19 +51,14 @@ module Checkoff
53
51
  # @param portfolio_name [String]
54
52
  # @param workspace_name [String, nil]
55
53
  # @param extra_project_fields [Array<String>]
56
- # @sg-ignore
57
54
  #
58
- # @sg-ignore
59
55
  # @return [Boolean]
60
56
  def evaluate(project, portfolio_name, workspace_name: nil, extra_project_fields: [])
61
57
  workspace_name ||= project.workspace&.name
62
- # @sg-ignore
63
- workspace_name ||= @workspaces.default_workspace.name
64
- # @sg-ignore
65
- projects = @portfolios.projects_in_portfolio(workspace_name, portfolio_name,
66
- extra_project_fields:)
67
- # @sg-ignore
68
- projects.any? { |p| p.name == project.name }
58
+ workspace_name ||= workspaces.default_workspace.name
59
+ matching_projects = portfolios.projects_in_portfolio(T.must(workspace_name), portfolio_name,
60
+ extra_project_fields:)
61
+ matching_projects.any? { |p| p.name == project.name }
69
62
  end
70
63
  end
71
64
  end
@@ -22,13 +22,16 @@ module Checkoff
22
22
  super()
23
23
  end
24
24
 
25
+ # @return [Checkoff::Sections]
26
+ attr_reader :sections
27
+
28
+ # @return [Asana::Client]
29
+ attr_reader :client
30
+
25
31
  private
26
32
 
27
33
  # @return [Array(Symbol, Array)]
28
34
  attr_reader :selector
29
-
30
- # @return [Asana::Client]
31
- attr_reader :client
32
35
  end
33
36
  end
34
37
  end
@@ -18,7 +18,7 @@ module Checkoff
18
18
  # @param section [Asana::Resources::Section]
19
19
  #
20
20
  # @return [Boolean]
21
- # @sg-ignore
21
+ # @sg-ignore String, nil safe-nav chain isn't inferred as returning Boolean
22
22
  def evaluate(section)
23
23
  tasks = client.tasks.get_tasks(section: section.gid,
24
24
  per_page: 100,
@@ -37,11 +37,9 @@ module Checkoff
37
37
 
38
38
  # @param section [Asana::Resources::Section]
39
39
  #
40
- # @sg-ignore
41
40
  # @return [Boolean]
42
41
  def evaluate(section)
43
- # @sg-ignore
44
- @sections.tasks_by_section_gid(section.gid).any?
42
+ sections.tasks_by_section_gid(section.gid).any?
45
43
  end
46
44
  end
47
45
  end
@@ -24,8 +24,12 @@ module Checkoff
24
24
  # @type [Hash{'unwrapped' => Hash}]
25
25
  task_data = tasks.task_to_h(task)
26
26
  # @type [Hash{'membership_by_project_name' => Hash}]
27
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
28
+ # https://github.com/castwide/solargraph/pull/1228
27
29
  unwrapped = task_data.fetch('unwrapped')
28
30
  # @type [Array]
31
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
32
+ # https://github.com/castwide/solargraph/pull/1228
29
33
  projects = unwrapped.fetch('membership_by_project_name').keys
30
34
  !(projects - [:my_tasks]).empty?
31
35
  end
@@ -45,10 +49,18 @@ module Checkoff
45
49
  # @param task [Asana::Resources::Task]
46
50
  # @param section_name_prefix [String]
47
51
  # @return [Boolean]
48
- # @sg-ignore
49
52
  def evaluate(task, section_name_prefix)
53
+ # @type [Hash{'unwrapped' => Hash}]
50
54
  task_data = tasks.task_to_h(task)
51
- task_data.fetch('unwrapped').fetch('membership_by_section_name').keys.any? do |section_name|
55
+ # @type [Hash{'membership_by_section_name' => Hash}]
56
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
57
+ # https://github.com/castwide/solargraph/pull/1228
58
+ unwrapped = task_data.fetch('unwrapped')
59
+ # @type [Array]
60
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
61
+ # https://github.com/castwide/solargraph/pull/1228
62
+ section_names = unwrapped.fetch('membership_by_section_name').keys
63
+ section_names.any? do |section_name|
52
64
  String(section_name).start_with?(section_name_prefix)
53
65
  end
54
66
  end
@@ -65,13 +77,21 @@ module Checkoff
65
77
  false
66
78
  end
67
79
 
68
- # @sg-ignore
69
80
  # @param task [Asana::Resources::Task]
70
81
  # @param section_name [String]
71
82
  # @return [Boolean]
72
83
  def evaluate(task, section_name)
84
+ # @type [Hash{'unwrapped' => Hash}]
73
85
  task_data = tasks.task_to_h(task)
74
- task_data.fetch('unwrapped').fetch('membership_by_section_name').keys.any?(section_name)
86
+ # @type [Hash{'membership_by_section_name' => Hash}]
87
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
88
+ # https://github.com/castwide/solargraph/pull/1228
89
+ unwrapped = task_data.fetch('unwrapped')
90
+ # @type [Array]
91
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
92
+ # https://github.com/castwide/solargraph/pull/1228
93
+ section_names = unwrapped.fetch('membership_by_section_name').keys
94
+ section_names.any?(section_name)
75
95
  end
76
96
  end
77
97
 
@@ -89,13 +109,15 @@ module Checkoff
89
109
  # @param task [Asana::Resources::Task]
90
110
  # @param project_name [String]
91
111
  # @return [Boolean]
92
- # @sg-ignore
112
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
113
+ # https://github.com/castwide/solargraph/pull/1228
93
114
  def evaluate(task, project_name)
94
115
  project_names = task.memberships.map do |membership|
95
- m = T.cast(membership, T::Hash[String, T.untyped])
96
- T.cast(m.fetch('project'), T::Hash[String, T.untyped]).fetch('name')
116
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
117
+ # https://github.com/castwide/solargraph/pull/1228
118
+ membership.fetch('project').fetch('name')
97
119
  end
98
- T.cast(project_names.include?(project_name), T::Boolean)
120
+ project_names.include?(project_name)
99
121
  end
100
122
  end
101
123
 
@@ -132,9 +154,11 @@ module Checkoff
132
154
  # @param task [Asana::Resources::Task]
133
155
  # @param tag_name [String]
134
156
  # @return [Boolean]
135
- # @sg-ignore
136
157
  def evaluate(task, tag_name)
137
- task.tags.map(&:name).include? tag_name
158
+ # @type [Array<Asana::Resources::Tag>]
159
+ tags = task.tags.to_a
160
+ tag_names = tags.map(&:name)
161
+ tag_names.include?(tag_name)
138
162
  end
139
163
  end
140
164
 
@@ -235,13 +259,13 @@ module Checkoff
235
259
 
236
260
  # @type [Array<Asana::Resources::Story>]
237
261
  stories = task.stories(per_page: 100).to_a.reject do |story|
238
- # @sg-ignore
262
+ # @sg-ignore story's inferred block param type regressed on rbs >= 4.1.0, fix in progress upstream
263
+ # https://github.com/castwide/solargraph/pull/1228
239
264
  excluding_resource_subtypes.include? story.resource_subtype
240
265
  end
241
266
  return true if stories.empty? # no stories == infinitely old!
242
267
 
243
- last_story = stories.last
244
- # @sg-ignore
268
+ last_story = T.must(stories.last)
245
269
  last_story_created_at = Time.parse(last_story.created_at)
246
270
  n_days_ago = Time.at(Time.now.to_i - (num_days * 86_400))
247
271
  last_story_created_at < n_days_ago
@@ -362,11 +386,13 @@ module Checkoff
362
386
  # @param task [Asana::Resources::Task]
363
387
  #
364
388
  # @return [Boolean]
365
- # @sg-ignore
389
+ # @sg-ignore Checkoff::SelectorClasses::Task::MilestonePFunctionEvaluator#evaluate
390
+ # return type could not be inferred
366
391
  def evaluate(task)
367
- raise 'Please add resource_subtype to extra_fields' if task.resource_subtype.nil?
392
+ resource_subtype = task.resource_subtype
393
+ raise 'Please add resource_subtype to extra_fields' if resource_subtype.nil?
368
394
 
369
- task.resource_subtype == 'milestone'
395
+ resource_subtype == 'milestone'
370
396
  end
371
397
  end
372
398
 
@@ -11,13 +11,11 @@ module Checkoff
11
11
 
12
12
  function_evaluators.each do |evaluator_class|
13
13
  # @type [Checkoff::SelectorClasses::FunctionEvaluator]
14
- # @sg-ignore
15
14
  evaluator = evaluator_class.new(selector:,
16
15
  **initializer_kwargs)
17
16
 
18
17
  next unless evaluator.matches?
19
18
 
20
- # @sg-ignore
21
19
  return try_this_evaluator(selector, evaluator)
22
20
  end
23
21
 
@@ -32,18 +30,18 @@ module Checkoff
32
30
  end
33
31
 
34
32
  # @return [Array<Class<Checkoff::SelectorClasses::FunctionEvaluator>>]
35
- # @sg-ignore
33
+ # @sg-ignore abstract method always raises; declared type documents the override contract, not this body
36
34
  def function_evaluators
37
35
  raise 'Implement me!'
38
36
  end
39
37
 
40
- # @param selector [Array]
38
+ # @param selector [Symbol, Array]
41
39
  # @param evaluator [Checkoff::SelectorClasses::FunctionEvaluator]
42
40
  # @return [Array]
43
41
  def evaluate_args(selector, evaluator)
44
42
  return [] unless selector.is_a?(Array)
45
43
 
46
- # @sg-ignore
44
+ # @sg-ignore Array#[] with an open-ended range is inferred as nilable even though it never is here
47
45
  selector[1..].map.with_index do |item, index|
48
46
  if evaluator.evaluate_arg?(index)
49
47
  evaluate(item)
@@ -53,7 +51,7 @@ module Checkoff
53
51
  end
54
52
  end
55
53
 
56
- # @param selector [Array]
54
+ # @param selector [Symbol, Array]
57
55
  # @param evaluator [Checkoff::SelectorClasses::FunctionEvaluator]
58
56
  # @return [Boolean, Object, nil]
59
57
  def try_this_evaluator(selector, evaluator)
@@ -19,14 +19,16 @@ module Checkoff
19
19
 
20
20
  # @param task [Asana::Resources::Task]
21
21
  # @return [Time, nil]
22
- # @sg-ignore
22
+ # @sg-ignore return type could not be inferred — #to_time called via &. on a Date|Time
23
+ # union receiver
23
24
  def start_time(task)
24
25
  date_or_time_field_by_name(task, :start)&.to_time
25
26
  end
26
27
 
27
28
  # @param task [Asana::Resources::Task]
28
- # @sg-ignore
29
29
  # @return [Time, nil]
30
+ # @sg-ignore return type could not be inferred — #to_time called via &. on a Date|Time
31
+ # union receiver
30
32
  def due_time(task)
31
33
  date_or_time_field_by_name(task, :due)&.to_time
32
34
  end
@@ -35,8 +37,7 @@ module Checkoff
35
37
  #
36
38
  # @return [Date, Time, nil]
37
39
  def start_date_or_time(task)
38
- # @sg-ignore
39
- return @time_class.parse(task.start_at).localtime unless task.start_at.nil?
40
+ return @time_class.parse(T.must(task.start_at)).localtime unless task.start_at.nil?
40
41
 
41
42
  return @date_class.parse(task.start_on) unless task.start_on.nil?
42
43
 
@@ -58,7 +59,7 @@ module Checkoff
58
59
  #
59
60
  # @return [Time, nil]
60
61
  def modified_time(task)
61
- @time_class.parse(task.modified_at).localtime unless task.modified_at.nil?
62
+ @time_class.parse(T.must(task.modified_at)).localtime unless task.modified_at.nil?
62
63
  end
63
64
 
64
65
  # @param task [Asana::Resources::Task]
@@ -10,7 +10,8 @@ module Checkoff
10
10
  # @param value [Object,Boolean]
11
11
  # @yieldreturn [generic<T>]
12
12
  # @return [generic<T>]
13
- # @sg-ignore
13
+ # @sg-ignore return type could not be inferred — Solargraph's generic/yield-return
14
+ # inference gap for #with_thread_local_variable
14
15
  def with_thread_local_variable(name, value, &block)
15
16
  old_value = Thread.current[name]
16
17
  Thread.current[name] = value
@@ -67,7 +67,9 @@ module Checkoff
67
67
  sections.each_entry { |section| by_section[section_key(section.name)] = [] }
68
68
  tasks.each do |task|
69
69
  assignee_section = task.assignee_section
70
- # @sg-ignore
70
+ # @sg-ignore assignee_section can be nil (task has no My Tasks section) but is
71
+ # dereferenced without a nil guard — looks like a real gap, flagging for the
72
+ # follow-up code PR
71
73
  current_section = section_key(assignee_section.name)
72
74
  by_section[current_section] ||= []
73
75
  by_section[current_section] << task
@@ -47,7 +47,7 @@ module Checkoff
47
47
  # @param portfolio_name [String]
48
48
  #
49
49
  # @return [Asana::Resources::Portfolio]
50
- # @sg-ignore
50
+ # @sg-ignore nil check above is not flow-sensitive
51
51
  def portfolio_or_raise(workspace_name, portfolio_name)
52
52
  portfolio_obj = portfolio(workspace_name, portfolio_name)
53
53
  raise "Could not find portfolio #{portfolio_name} under workspace #{workspace_name}." if portfolio_obj.nil?
@@ -108,8 +108,9 @@ module Checkoff
108
108
  else
109
109
  # @type [Enumerable<Asana::Resources::Project>]
110
110
  ps = projects_by_workspace_name(workspace_name, extra_fields:)
111
- # @type <Asana::Resources::Project,nil>
112
- # @sg-ignore
111
+ # @type [Asana::Resources::Project,nil]
112
+ # @sg-ignore Variable type could not be inferred / Unresolved call to _1 — Solargraph
113
+ # doesn't fully support numbered block parameters
113
114
  project = ps.find { _1.name == project_name }
114
115
  project_by_gid(project.gid, extra_fields:) unless project.nil?
115
116
  end
@@ -120,8 +121,8 @@ module Checkoff
120
121
  # @param project_name [String,Symbol] - :my_tasks or a project name
121
122
  # @param [Array<String>] extra_fields
122
123
  #
123
- # @sg-ignore
124
124
  # @return [Asana::Resources::Project]
125
+ # @sg-ignore nil check above is not flow-sensitive
125
126
  def project_or_raise(workspace_name, project_name, extra_fields: [])
126
127
  p = project(workspace_name, project_name, extra_fields:)
127
128
  raise "Could not find project #{project_name.inspect} under workspace #{workspace_name}." if p.nil?
@@ -80,7 +80,7 @@ module Checkoff
80
80
 
81
81
  # Given a workspace name and project name, then provide a Hash of
82
82
  # tasks with section name -> task list of the uncompleted tasks
83
- # @param workspace_name [String]
83
+ # @param workspace_name [String, Symbol]
84
84
  # @param project_name [String, Symbol]
85
85
  # @param only_uncompleted [Boolean]
86
86
  # @param extra_fields [Array<String>]
@@ -142,11 +142,10 @@ module Checkoff
142
142
  # @param section_name [String, nil]
143
143
  #
144
144
  # @return [Array<String>]
145
- # @sg-ignore
145
+ # @sg-ignore Task#name is nilable; task names are assumed present here
146
146
  def section_task_names(workspace_name, project_name, section_name)
147
147
  task_array = tasks(workspace_name, project_name, section_name)
148
- # @type [Array<String>]
149
- T.cast(task_array.map(&:name), T::Array[String])
148
+ T.cast(task_array.map(&:name), Array)
150
149
  end
151
150
  cache_method :section_task_names, SHORT_CACHE_TIME
152
151
 
@@ -184,17 +183,15 @@ module Checkoff
184
183
  #
185
184
  # @return [Asana::Resources::Section, nil]
186
185
  def previous_section(section)
187
- sections = sections_by_project_gid(section.project.fetch('gid'))
186
+ project_sections = sections_by_project_gid(section.project.fetch('gid'))
188
187
 
189
188
  # @type [Array<Asana::Resources::Section>]
190
- # @sg-ignore
191
- sections = sections.to_a
189
+ project_sections_arr = project_sections.to_a
192
190
 
193
- index = sections.find_index { |s| s.gid == section.gid }
191
+ index = project_sections_arr.find_index { |s| s.gid == section.gid }
194
192
  return nil if index.nil? || index.zero?
195
193
 
196
- # @sg-ignore
197
- sections[index - 1]
194
+ project_sections_arr[index - 1]
198
195
  end
199
196
  cache_method :previous_section, SHORT_CACHE_TIME
200
197
 
@@ -295,7 +292,6 @@ module Checkoff
295
292
  # @type [String, nil]
296
293
  current_section = section_key(section_name)
297
294
 
298
- # @sg-ignore
299
295
  by_section.fetch(current_section) << task
300
296
  end
301
297
 
@@ -89,7 +89,8 @@ module Checkoff
89
89
  # as memberships with a separate API within a task.
90
90
  #
91
91
  # @param subtask [Asana::Resources::Task]
92
- # @sg-ignore
92
+ # @return [Boolean, nil]
93
+ # @sg-ignore Checkoff::Subtasks#subtask_section? return type could not be inferred
93
94
  def subtask_section?(subtask)
94
95
  subtask.is_rendered_as_separator
95
96
  end
data/lib/checkoff/tags.rb CHANGED
@@ -79,7 +79,7 @@ module Checkoff
79
79
  # @param tag_name [String]
80
80
  #
81
81
  # @return [Asana::Resources::Tag]
82
- # @sg-ignore
82
+ # @sg-ignore nil check above is not flow-sensitive
83
83
  def tag_or_raise(workspace_name, tag_name)
84
84
  t = tag(workspace_name, tag_name)
85
85
 
@@ -114,7 +114,8 @@ module Checkoff
114
114
  # @return [Hash{Symbol => Object}]
115
115
  def build_params(options)
116
116
  { limit: options[:per_page], completed_since: options[:completed_since] }.reject do |_, v|
117
- # @sg-ignore
117
+ # @sg-ignore Unresolved call to nil? on Object — Solargraph doesn't treat a Hash's
118
+ # declared `Object` value type as full Object here
118
119
  v.nil? || Array(v).empty?
119
120
  end
120
121
  end
@@ -73,7 +73,9 @@ module Checkoff
73
73
  workspace = workspaces.workspace_or_raise(workspace_name)
74
74
  api_params, task_selector = @search_url_parser.convert_params(url)
75
75
  debug { "Task search params: api_params=#{api_params}, task_selector=#{task_selector}" }
76
- # @sg-ignore
76
+ # @sg-ignore task_selector expected Symbol,Array<Symbol,Integer,Array> received
77
+ # Hash{String=>String} — Solargraph mis-assigns tuple-destructured types from
78
+ # `api_params, task_selector = @search_url_parser.convert_params(url)` above
77
79
  raw_task_search(api_params, workspace_gid: workspace.gid, task_selector:,
78
80
  extra_fields:)
79
81
  end
@@ -186,8 +188,9 @@ module Checkoff
186
188
  end
187
189
 
188
190
  # @param [Array<String>] extra_fields
189
- # @sg-ignore
190
- # @return [Hash{Symbol => undefined}]
191
+ # @return [Hash{Symbol => Array<String>}]
192
+ # @sg-ignore return type could not be inferred — bracket access on a Hash{Symbol=>undefined}
193
+ # local doesn't narrow even with a T.cast on the return expression
191
194
  def calculate_api_options(extra_fields)
192
195
  # @type [Hash{Symbol => undefined}]
193
196
  all_options = projects.task_options(extra_fields: ['custom_fields'] + extra_fields)
@@ -64,19 +64,19 @@ module Checkoff
64
64
  # :nocov:
65
65
  class << self
66
66
  # @return [String]
67
- # @sg-ignore
67
+ # @sg-ignore `x || raise(...)` isn't inferred as narrowing away the nil branch
68
68
  def project_name
69
69
  ARGV[1] || raise('Please pass project name to pull tasks from as first argument')
70
70
  end
71
71
 
72
- # @sg-ignore
73
72
  # @return [String]
73
+ # @sg-ignore `x || raise(...)` isn't inferred as narrowing away the nil branch
74
74
  def workspace_name
75
75
  ARGV[0] || raise('Please pass workspace name as first argument')
76
76
  end
77
77
 
78
78
  # @return [Array(Symbol, Array)]
79
- # @sg-ignore
79
+ # @sg-ignore `x || raise(...)` isn't inferred as narrowing away the nil branch
80
80
  def task_selector
81
81
  task_selector_json = ARGV[2] || raise('Please pass task_selector in JSON form as third argument')
82
82