checkoff 0.258.0 → 0.261.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/lib/checkoff/attachments.rb +14 -15
  3. data/lib/checkoff/cli.rb +7 -12
  4. data/lib/checkoff/clients.rb +0 -2
  5. data/lib/checkoff/custom_fields.rb +13 -24
  6. data/lib/checkoff/internal/asana_event_enrichment.rb +11 -21
  7. data/lib/checkoff/internal/asana_event_filter.rb +1 -5
  8. data/lib/checkoff/internal/config_loader.rb +0 -2
  9. data/lib/checkoff/internal/logging.rb +0 -2
  10. data/lib/checkoff/internal/project_hashes.rb +26 -4
  11. data/lib/checkoff/internal/search_url/custom_field_param_converter.rb +9 -10
  12. data/lib/checkoff/internal/search_url/custom_field_variant.rb +7 -2
  13. data/lib/checkoff/internal/search_url/date_param_converter.rb +13 -10
  14. data/lib/checkoff/internal/search_url/parser.rb +2 -7
  15. data/lib/checkoff/internal/search_url/simple_param_converter.rb +1 -4
  16. data/lib/checkoff/internal/section_selector_evaluator.rb +1 -1
  17. data/lib/checkoff/internal/selector_classes/common/function_evaluator.rb +2 -2
  18. data/lib/checkoff/internal/selector_classes/common.rb +2 -3
  19. data/lib/checkoff/internal/selector_classes/function_evaluator.rb +9 -4
  20. data/lib/checkoff/internal/selector_classes/project/function_evaluator.rb +2 -2
  21. data/lib/checkoff/internal/selector_classes/project.rb +0 -1
  22. data/lib/checkoff/internal/selector_classes/section/function_evaluator.rb +2 -2
  23. data/lib/checkoff/internal/selector_classes/section.rb +1 -2
  24. data/lib/checkoff/internal/selector_classes/task/function_evaluator.rb +2 -2
  25. data/lib/checkoff/internal/selector_classes/task.rb +2 -26
  26. data/lib/checkoff/internal/selector_evaluator.rb +6 -5
  27. data/lib/checkoff/internal/task_hashes.rb +17 -9
  28. data/lib/checkoff/internal/task_timing.rb +0 -4
  29. data/lib/checkoff/internal/thread_local.rb +0 -2
  30. data/lib/checkoff/monkeypatches/resource_marshalling.rb +3 -3
  31. data/lib/checkoff/my_tasks.rb +1 -4
  32. data/lib/checkoff/portfolios.rb +0 -1
  33. data/lib/checkoff/project_selectors.rb +1 -1
  34. data/lib/checkoff/projects.rb +19 -8
  35. data/lib/checkoff/resources.rb +14 -3
  36. data/lib/checkoff/sections.rb +9 -9
  37. data/lib/checkoff/subtasks.rb +1 -2
  38. data/lib/checkoff/tags.rb +3 -4
  39. data/lib/checkoff/task_searches.rb +1 -8
  40. data/lib/checkoff/task_selectors.rb +25 -12
  41. data/lib/checkoff/tasks.rb +6 -14
  42. data/lib/checkoff/timelines.rb +3 -20
  43. data/lib/checkoff/timing.rb +1 -2
  44. data/lib/checkoff/version.rb +1 -1
  45. data/lib/checkoff/workspaces.rb +1 -7
  46. data/rbi/checkoff.rbi +99 -146
  47. data/sig/checkoff.rbs +86 -153
  48. metadata +2 -2
@@ -8,7 +8,7 @@ module Checkoff
8
8
  module Common
9
9
  # Base class to evaluate a project selector function given fully evaluated arguments
10
10
  class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
11
- # @param selector [Array(Symbol, Array), String]
11
+ # @param selector [Array<Symbol, Array>, Symbol]
12
12
  # @param custom_fields [Checkoff::CustomFields]
13
13
  # @param _kwargs [Hash]
14
14
  def initialize(selector:, custom_fields:, **_kwargs)
@@ -22,7 +22,7 @@ module Checkoff
22
22
 
23
23
  private
24
24
 
25
- # @return [Array(Symbol, Array), String]
25
+ # @return [Array<Symbol, Array>, Symbol]
26
26
  attr_reader :selector
27
27
  end
28
28
  end
@@ -223,8 +223,6 @@ module Checkoff
223
223
  # @param resource [Asana::Resources::Task, Asana::Resources::Project]
224
224
  # @param prefix [String]
225
225
  # @return [boolish]
226
- # @sg-ignore Checkoff::SelectorClasses::Common::NameStartsWithPFunctionEvaluator#evaluate
227
- # return type could not be inferred
228
226
  def evaluate(resource, prefix)
229
227
  resource.name&.start_with?(prefix)
230
228
  end
@@ -238,8 +236,9 @@ module Checkoff
238
236
 
239
237
  # @param _resource [Asana::Resources::Task,Asana::Resources::Project]
240
238
  # @return [String]
241
- # @sg-ignore selector is only String here per matches?'s is_a?(String) check, in another method
242
239
  def evaluate(_resource)
240
+ raise "Expected a String selector, got #{selector.inspect}" unless selector.is_a?(String)
241
+
243
242
  selector
244
243
  end
245
244
  end
@@ -26,7 +26,6 @@ module Checkoff
26
26
  end
27
27
 
28
28
  # @return [Boolean]
29
- # @sg-ignore return type could not be inferred — abstract method body is only `raise`
30
29
  def matches?
31
30
  raise 'Override me!'
32
31
  end
@@ -34,17 +33,23 @@ module Checkoff
34
33
  # @param _task [Asana::Resources::Task]
35
34
  # @param _args [Array<Object>]
36
35
  # @return [Object]
37
- # @sg-ignore return type could not be inferred — abstract method body is only `raise`
38
36
  def evaluate(_task, *_args)
39
37
  raise 'Implement me!'
40
38
  end
41
39
 
42
40
  private
43
41
 
44
- # @param object [Object]
42
+ # @param object [Array<Symbol, Array>, Symbol]
45
43
  # @param fn_name [Symbol]
44
+ # @return [Boolean]
46
45
  def fn?(object, fn_name)
47
- object.is_a?(Array) && !object.empty? && [fn_name, fn_name.to_s].include?(object[0])
46
+ if object.is_a?(Array) && !object.empty?
47
+ # @type [Array<Symbol, Array>]
48
+ array = object
49
+ [fn_name, fn_name.to_s].include?(array[0])
50
+ else
51
+ false
52
+ end
48
53
  end
49
54
  end
50
55
  end
@@ -8,7 +8,7 @@ module Checkoff
8
8
  module Project
9
9
  # Base class to evaluate a project selector function given fully evaluated arguments
10
10
  class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
11
- # @param selector [Array(Symbol, Array), String]
11
+ # @param selector [Array<Symbol, Array>, Symbol]
12
12
  # @param projects [Checkoff::Projects]
13
13
  # @param portfolios [Checkoff::Portfolios]
14
14
  # @param workspaces [Checkoff::Workspaces]
@@ -36,7 +36,7 @@ module Checkoff
36
36
 
37
37
  private
38
38
 
39
- # @return [Array(Symbol, Array)]
39
+ # @return [Array<Symbol, Array>, Symbol]
40
40
  attr_reader :selector
41
41
  end
42
42
  end
@@ -17,7 +17,6 @@ module Checkoff
17
17
 
18
18
  # @param resource [Asana::Resources::Project]
19
19
  # @return [String, nil]
20
- # @sg-ignore nilable return from an @!parse-declared method isn't inferred
21
20
  def evaluate(resource)
22
21
  resource.due_date
23
22
  end
@@ -8,7 +8,7 @@ module Checkoff
8
8
  module Section
9
9
  # Base class to evaluate a project selector function given fully evaluated arguments
10
10
  class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
11
- # @param selector [Array(Symbol, Array), String]
11
+ # @param selector [Array<Symbol, Array>, Symbol]
12
12
  # @param client [Asana::Client]
13
13
  # @param sections [Checkoff::Sections]
14
14
  # @param kwargs [Hash]
@@ -30,7 +30,7 @@ module Checkoff
30
30
 
31
31
  private
32
32
 
33
- # @return [Array(Symbol, Array)]
33
+ # @return [Array<Symbol, Array>, Symbol]
34
34
  attr_reader :selector
35
35
  end
36
36
  end
@@ -18,12 +18,11 @@ module Checkoff
18
18
  # @param section [Asana::Resources::Section]
19
19
  #
20
20
  # @return [Boolean]
21
- # @sg-ignore String, nil safe-nav chain isn't inferred as returning Boolean
22
21
  def evaluate(section)
23
22
  tasks = client.tasks.get_tasks(section: section.gid,
24
23
  per_page: 100,
25
24
  options: { fields: ['resource_subtype'] })
26
- T.unsafe(tasks).last&.resource_subtype == 'milestone'
25
+ tasks.last&.resource_subtype == 'milestone'
27
26
  end
28
27
  end
29
28
 
@@ -8,7 +8,7 @@ module Checkoff
8
8
  module Task
9
9
  # Base class to evaluate a task selector function given fully evaluated arguments
10
10
  class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
11
- # @param selector [Array(Symbol, Array), String]
11
+ # @param selector [Array<Symbol, Array>, Symbol]
12
12
  # @param tasks [Checkoff::Tasks]
13
13
  # @param timelines [Checkoff::Timelines]
14
14
  # @param custom_fields [Checkoff::CustomFields]
@@ -36,7 +36,7 @@ module Checkoff
36
36
 
37
37
  private
38
38
 
39
- # @return [Array(Symbol, Array)]
39
+ # @return [Array<Symbol, Array>, Symbol]
40
40
  attr_reader :selector
41
41
  end
42
42
  end
@@ -24,12 +24,8 @@ 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
29
27
  unwrapped = task_data.fetch('unwrapped')
30
28
  # @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
33
29
  projects = unwrapped.fetch('membership_by_project_name').keys
34
30
  !(projects - [:my_tasks]).empty?
35
31
  end
@@ -53,12 +49,8 @@ module Checkoff
53
49
  # @type [Hash{'unwrapped' => Hash}]
54
50
  task_data = tasks.task_to_h(task)
55
51
  # @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
52
  unwrapped = task_data.fetch('unwrapped')
59
53
  # @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
54
  section_names = unwrapped.fetch('membership_by_section_name').keys
63
55
  section_names.any? do |section_name|
64
56
  String(section_name).start_with?(section_name_prefix)
@@ -84,12 +76,8 @@ module Checkoff
84
76
  # @type [Hash{'unwrapped' => Hash}]
85
77
  task_data = tasks.task_to_h(task)
86
78
  # @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
79
  unwrapped = task_data.fetch('unwrapped')
90
80
  # @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
81
  section_names = unwrapped.fetch('membership_by_section_name').keys
94
82
  section_names.any?(section_name)
95
83
  end
@@ -109,12 +97,8 @@ module Checkoff
109
97
  # @param task [Asana::Resources::Task]
110
98
  # @param project_name [String]
111
99
  # @return [Boolean]
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
114
100
  def evaluate(task, project_name)
115
101
  project_names = task.memberships.map do |membership|
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
102
  membership.fetch('project').fetch('name')
119
103
  end
120
104
  project_names.include?(project_name)
@@ -214,10 +198,8 @@ module Checkoff
214
198
 
215
199
  # @param task [Asana::Resources::Task]
216
200
  # @return [Boolean]
217
- # @sg-ignore Checkoff::SelectorClasses::Task::UnassignedPFunctionEvaluator#evaluate
218
- # return type could not be inferred
219
201
  def evaluate(task)
220
- T.cast(task.assignee.nil? == true, T::Boolean)
202
+ task.assignee.nil?
221
203
  end
222
204
  end
223
205
 
@@ -231,10 +213,8 @@ module Checkoff
231
213
 
232
214
  # @param task [Asana::Resources::Task]
233
215
  # @return [Boolean]
234
- # @sg-ignore Checkoff::SelectorClasses::Task::DueDateSetPFunctionEvaluator#evaluate
235
- # return type could not be inferred
236
216
  def evaluate(task)
237
- T.cast(!(task.due_at.nil? && task.due_on.nil?), T::Boolean)
217
+ !(task.due_at.nil? && task.due_on.nil?)
238
218
  end
239
219
  end
240
220
 
@@ -259,8 +239,6 @@ module Checkoff
259
239
 
260
240
  # @type [Array<Asana::Resources::Story>]
261
241
  stories = task.stories(per_page: 100).to_a.reject do |story|
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
264
242
  excluding_resource_subtypes.include? story.resource_subtype
265
243
  end
266
244
  return true if stories.empty? # no stories == infinitely old!
@@ -386,8 +364,6 @@ module Checkoff
386
364
  # @param task [Asana::Resources::Task]
387
365
  #
388
366
  # @return [Boolean]
389
- # @sg-ignore Checkoff::SelectorClasses::Task::MilestonePFunctionEvaluator#evaluate
390
- # return type could not be inferred
391
367
  def evaluate(task)
392
368
  resource_subtype = task.resource_subtype
393
369
  raise 'Please add resource_subtype to extra_fields' if resource_subtype.nil?
@@ -1,10 +1,10 @@
1
- # typed: false
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Checkoff
5
5
  # Base class to evaluate Asana resource selectors against an Asana resource
6
6
  class SelectorEvaluator
7
- # @param selector [Symbol, Array<Symbol, Integer, Array>]
7
+ # @param selector [Symbol, Array<Symbol, String, Integer, Array>]
8
8
  # @return [Boolean, Object, Array, nil]
9
9
  def evaluate(selector)
10
10
  return true if selector.empty?
@@ -30,7 +30,6 @@ module Checkoff
30
30
  end
31
31
 
32
32
  # @return [Array<Class<Checkoff::SelectorClasses::FunctionEvaluator>>]
33
- # @sg-ignore abstract method always raises; declared type documents the override contract, not this body
34
33
  def function_evaluators
35
34
  raise 'Implement me!'
36
35
  end
@@ -41,8 +40,10 @@ module Checkoff
41
40
  def evaluate_args(selector, evaluator)
42
41
  return [] unless selector.is_a?(Array)
43
42
 
44
- # @sg-ignore Array#[] with an open-ended range is inferred as nilable even though it never is here
45
- selector[1..].map.with_index do |item, index|
43
+ rest = selector[1..]
44
+ return [] unless rest
45
+
46
+ rest.map.with_index do |item, index|
46
47
  if evaluator.evaluate_arg?(index)
47
48
  evaluate(item)
48
49
  else
@@ -9,7 +9,7 @@ module Checkoff
9
9
  # @param task [Asana::Resources::Task]
10
10
  # @return [Hash]
11
11
  def task_to_h(task)
12
- # @type [Hash]
12
+ # @type [Hash{String => Object}]
13
13
  task_hash = task.to_h
14
14
  task_hash['unwrapped'] = {}
15
15
  unwrap_custom_fields(task_hash)
@@ -18,7 +18,7 @@ module Checkoff
18
18
  task_hash
19
19
  end
20
20
 
21
- # @param task_data [Hash]
21
+ # @param task_data [Hash{String => Object}]
22
22
  # @param client [Asana::Client]
23
23
  #
24
24
  # @return [Asana::Resources::Task]
@@ -34,7 +34,7 @@ module Checkoff
34
34
  # @param task_hash [Hash]
35
35
  # @return [void]
36
36
  def unwrap_custom_fields(task_hash)
37
- # @type [Array<Hash>,nil]
37
+ # @type [Array<Hash{String => Object}>,nil]
38
38
  custom_fields = task_hash.fetch('custom_fields', nil)
39
39
 
40
40
  return if custom_fields.nil?
@@ -53,8 +53,7 @@ module Checkoff
53
53
  return unless task_hash.key? 'assignee_section'
54
54
 
55
55
  assignee_section = task_hash.fetch('assignee_section')
56
- # @type [Hash{String => String}]
57
- assignee = T.cast(task_hash.fetch('assignee'), T::Hash[String, String])
56
+ assignee = assignee_hash(task_hash)
58
57
  memberships << {
59
58
  'section' => assignee_section.dup,
60
59
  'project' => {
@@ -64,24 +63,33 @@ module Checkoff
64
63
  }
65
64
  end
66
65
 
67
- # @param task_hash [Hash]
66
+ # @param task_hash [Hash{String => String, Hash, Array}]
67
+ # @return [Hash{String => Object}]
68
+ def assignee_hash(task_hash)
69
+ assignee = task_hash.fetch('assignee')
70
+ raise "Expected assignee to be a Hash, got #{assignee.class}" unless assignee.is_a?(Hash)
71
+
72
+ assignee
73
+ end
74
+
75
+ # @param task_hash [Hash{String => Object}]
68
76
  # @param resource [String]
69
77
  # @param memberships [Array<Hash>]
70
78
  # @param key [String]
71
79
  #
72
80
  # @return [void]
73
81
  def unwrap_memberships(task_hash, memberships, resource, key)
74
- # @type [Hash]
82
+ # @type [Hash{String => Object}]
75
83
  unwrapped = task_hash.fetch('unwrapped')
76
84
  unwrapped["membership_by_#{resource}_#{key}"] = memberships.group_by do |membership|
77
85
  membership[resource][key]
78
86
  end.transform_values(&:first)
79
87
  end
80
88
 
81
- # @param task_hash [Hash]
89
+ # @param task_hash [Hash{String => Object}]
82
90
  # @return [void]
83
91
  def unwrap_all_memberships(task_hash)
84
- # @type [Array<Hash>]
92
+ # @type [Array<Hash{String => Object}>]
85
93
  memberships = task_hash.fetch('memberships', []).dup
86
94
  add_user_task_list(task_hash, memberships)
87
95
  unwrap_memberships(task_hash, memberships, 'section', 'gid')
@@ -19,16 +19,12 @@ module Checkoff
19
19
 
20
20
  # @param task [Asana::Resources::Task]
21
21
  # @return [Time, nil]
22
- # @sg-ignore return type could not be inferred — #to_time called via &. on a Date|Time
23
- # union receiver
24
22
  def start_time(task)
25
23
  date_or_time_field_by_name(task, :start)&.to_time
26
24
  end
27
25
 
28
26
  # @param task [Asana::Resources::Task]
29
27
  # @return [Time, nil]
30
- # @sg-ignore return type could not be inferred — #to_time called via &. on a Date|Time
31
- # union receiver
32
28
  def due_time(task)
33
29
  date_or_time_field_by_name(task, :due)&.to_time
34
30
  end
@@ -10,8 +10,6 @@ module Checkoff
10
10
  # @param value [Object,Boolean]
11
11
  # @yieldreturn [generic<T>]
12
12
  # @return [generic<T>]
13
- # @sg-ignore return type could not be inferred — Solargraph's generic/yield-return
14
- # inference gap for #with_thread_local_variable
15
13
  def with_thread_local_variable(name, value, &block)
16
14
  old_value = Thread.current[name]
17
15
  Thread.current[name] = value
@@ -20,19 +20,19 @@ module Asana
20
20
  # Public: The base resource class which provides some sugar over common
21
21
  # resource functionality.
22
22
  class Resource
23
- # @return [Hash]
23
+ # @return [Hash{String => Object}]
24
24
  def marshal_dump
25
25
  { 'data' => @_data,
26
26
  'client' => @_client }
27
27
  end
28
28
 
29
- # @param data [Hash]
29
+ # @param data [Hash{String => Object}]
30
30
  #
31
31
  # @return [void]
32
32
  def marshal_load(data)
33
33
  # @type [Asana::Client]
34
34
  @_client = data.fetch('client')
35
- # @type [Hash]
35
+ # @type [Hash{String => Object}]
36
36
  @_data = data.fetch('data')
37
37
  @_data.each do |k, v|
38
38
  if respond_to?(k)
@@ -67,10 +67,7 @@ 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 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
73
- current_section = section_key(assignee_section.name)
70
+ current_section = section_key(assignee_section&.name)
74
71
  by_section[current_section] ||= []
75
72
  by_section[current_section] << task
76
73
  end
@@ -47,7 +47,6 @@ module Checkoff
47
47
  # @param portfolio_name [String]
48
48
  #
49
49
  # @return [Asana::Resources::Portfolio]
50
- # @sg-ignore nil check above is not flow-sensitive
51
50
  def portfolio_or_raise(workspace_name, portfolio_name)
52
51
  portfolio_obj = portfolio(workspace_name, portfolio_name)
53
52
  raise "Could not find portfolio #{portfolio_name} under workspace #{workspace_name}." if portfolio_obj.nil?
@@ -42,7 +42,7 @@ module Checkoff
42
42
  end
43
43
 
44
44
  # @param [Asana::Resources::Project] project
45
- # @param [Array(Symbol, Array)] project_selector Filter based on
45
+ # @param [Array<Symbol, String, Array>] project_selector Filter based on
46
46
  # project details. Examples: [:tag, 'foo'] [:not, [:tag, 'foo']] [:tag, 'foo']
47
47
  # @return [Boolean]
48
48
  def filter_via_project_selector(project, project_selector)
@@ -22,12 +22,19 @@ require 'asana'
22
22
  module Checkoff
23
23
  # Work with projects in Asana
24
24
  class Projects
25
+ # @return [Integer]
25
26
  MINUTE = 60
27
+ # @return [Integer]
26
28
  HOUR = MINUTE * 60
29
+ # @return [Integer]
27
30
  DAY = 24 * HOUR
31
+ # @return [Integer]
28
32
  REALLY_LONG_CACHE_TIME = MINUTE * 30
33
+ # @return [Integer]
29
34
  LONG_CACHE_TIME = MINUTE * 15
35
+ # @return [Integer]
30
36
  MEDIUM_CACHE_TIME = MINUTE * 5
37
+ # @return [Integer]
31
38
  SHORT_CACHE_TIME = MINUTE
32
39
 
33
40
  # @!parse
@@ -65,6 +72,12 @@ module Checkoff
65
72
 
66
73
  # Default options used in Asana API to pull tasks
67
74
  #
75
+ # Keys are :per_page (Integer), :options (a Hash whose :fields is an
76
+ # Array<String>) and, when only_uncompleted is set, :completed_since
77
+ # (String). The value type is genuinely per-key, which a single YARD
78
+ # Hash value type cannot express.
79
+ # Callers who want the field list should call #task_fields directly.
80
+ #
68
81
  # @param extra_fields [Array<String>]
69
82
  # @param [Boolean] only_uncompleted
70
83
  #
@@ -109,9 +122,7 @@ module Checkoff
109
122
  # @type [Enumerable<Asana::Resources::Project>]
110
123
  ps = projects_by_workspace_name(workspace_name, extra_fields:)
111
124
  # @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
114
- project = ps.find { _1.name == project_name }
125
+ project = ps.find { |p| p.name == project_name }
115
126
  project_by_gid(project.gid, extra_fields:) unless project.nil?
116
127
  end
117
128
  end
@@ -122,7 +133,6 @@ module Checkoff
122
133
  # @param [Array<String>] extra_fields
123
134
  #
124
135
  # @return [Asana::Resources::Project]
125
- # @sg-ignore nil check above is not flow-sensitive
126
136
  def project_or_raise(workspace_name, project_name, extra_fields: [])
127
137
  p = project(workspace_name, project_name, extra_fields:)
128
138
  raise "Could not find project #{project_name.inspect} under workspace #{workspace_name}." if p.nil?
@@ -199,9 +209,10 @@ module Checkoff
199
209
  cache_method :projects_by_workspace_name, REALLY_LONG_CACHE_TIME
200
210
 
201
211
  # @param project_obj [Asana::Resources::Project]
202
- # @param project [String, Symbol] - :not_specified, :my_tasks or a project name
212
+ # @param project [String, :not_specified, :my_tasks, nil] - a String is a
213
+ # project name
203
214
  #
204
- # @return [Hash]
215
+ # @return (see Checkoff::Internal::ProjectHashes#project_to_h)
205
216
  def project_to_h(project_obj, project: :not_specified)
206
217
  project_hashes.project_to_h(project_obj, project:)
207
218
  end
@@ -214,14 +225,14 @@ module Checkoff
214
225
  # date is today or in the past.
215
226
  #
216
227
  # @param project [Asana::Resources::Project]
217
- # @param period [Symbol, Array(Symbol, Integer)] See Checkoff::Timing#in_period? - :now_or_before,:this_week
228
+ # @param period [Symbol, Array<Symbol, Integer>] See Checkoff::Timing#in_period? - :now_or_before,:this_week
218
229
  def project_ready?(project, period: :now_or_before)
219
230
  in_period?(project, :ready, period)
220
231
  end
221
232
 
222
233
  # @param project [Asana::Resources::Project]
223
234
  # @param field_name [Symbol,Array]
224
- # @param period [Symbol, Array(Symbol,Integer)] See Checkoff::Timing#in_period? - :now_or_before,:this_week
235
+ # @param period [Symbol, Array<Symbol, Integer>] See Checkoff::Timing#in_period? - :now_or_before,:this_week
225
236
  def in_period?(project, field_name, period)
226
237
  # @type [Date,Time,nil]
227
238
  project_date = project_timing.date_or_time_field_by_name(project, field_name)
@@ -62,9 +62,7 @@ module Checkoff
62
62
  %w[task section project].each do |resource_type_to_try|
63
63
  next unless [resource_type_to_try, nil].include?(resource_type)
64
64
 
65
- # @type [Asana::Resources::Resource, nil]
66
- resource = T.cast(method(:"fetch_#{resource_type_to_try}_gid").call(gid),
67
- T.nilable(Asana::Resources::Resource))
65
+ resource = fetch_gid_by_type(resource_type_to_try, gid)
68
66
  return [resource, resource_type_to_try] if resource
69
67
  end
70
68
  [nil, nil]
@@ -72,6 +70,19 @@ module Checkoff
72
70
 
73
71
  private
74
72
 
73
+ # @param resource_type [String]
74
+ # @param gid [String]
75
+ #
76
+ # @return [Asana::Resources::Resource, nil]
77
+ def fetch_gid_by_type(resource_type, gid)
78
+ case resource_type
79
+ when 'task' then fetch_task_gid(gid)
80
+ when 'section' then fetch_section_gid(gid)
81
+ when 'project' then fetch_project_gid(gid)
82
+ else raise "Unexpected resource_type: #{resource_type}"
83
+ end
84
+ end
85
+
75
86
  # @param gid [String]
76
87
  #
77
88
  # @return [Asana::Resources::Task, nil]
@@ -14,10 +14,15 @@ module Checkoff
14
14
  # @!parse
15
15
  # extend CacheMethod::ClassMethods
16
16
 
17
+ # @return [Integer]
17
18
  MINUTE = 60
19
+ # @return [Integer]
18
20
  HOUR = MINUTE * 60
21
+ # @return [Integer]
19
22
  REALLY_LONG_CACHE_TIME = MINUTE * 30
23
+ # @return [Integer]
20
24
  LONG_CACHE_TIME = MINUTE * 15
25
+ # @return [Integer]
21
26
  SHORT_CACHE_TIME = MINUTE * 5
22
27
 
23
28
  extend Forwardable
@@ -142,10 +147,9 @@ module Checkoff
142
147
  # @param section_name [String, nil]
143
148
  #
144
149
  # @return [Array<String>]
145
- # @sg-ignore Task#name is nilable; task names are assumed present here
146
150
  def section_task_names(workspace_name, project_name, section_name)
147
151
  task_array = tasks(workspace_name, project_name, section_name)
148
- T.cast(task_array.map(&:name), Array)
152
+ task_array.filter_map(&:name)
149
153
  end
150
154
  cache_method :section_task_names, SHORT_CACHE_TIME
151
155
 
@@ -155,7 +159,6 @@ module Checkoff
155
159
  # @param extra_section_fields [Array<String>]
156
160
  #
157
161
  # @return [Asana::Resources::Section]
158
- # @sg-ignore section() is nil-checked below
159
162
  def section_or_raise(workspace_name, project_name, section_name, extra_section_fields: [])
160
163
  s = section(workspace_name, project_name, section_name,
161
164
  extra_section_fields:)
@@ -280,14 +283,12 @@ module Checkoff
280
283
  # @return [void]
281
284
  def file_task_by_section(by_section, task, project_gid)
282
285
  membership = task.memberships.find do |m|
283
- membership_hash = T.unsafe(m)
284
- T.cast(membership_hash['project'], T::Hash[String, T.untyped])['gid'] == project_gid
286
+ m.fetch('project')['gid'] == project_gid
285
287
  end
286
288
  raise "Could not find task in project_gid #{project_gid}: #{task}" if membership.nil?
287
289
 
288
- membership_data = T.unsafe(membership)
289
- section = T.cast(membership_data['section'], T::Hash[String, T.untyped])
290
- section_name = T.cast(section['name'], String)
290
+ section = membership.fetch('section')
291
+ section_name = section.fetch('name')
291
292
 
292
293
  # @type [String, nil]
293
294
  current_section = section_key(section_name)
@@ -298,7 +299,6 @@ module Checkoff
298
299
  # @param workspace_name [String, Symbol]
299
300
  # @param project_name [String, Symbol]
300
301
  # @return [Asana::Resources::Project]
301
- # @sg-ignore projects.project may be nil but is raised below
302
302
  def project_or_raise(workspace_name, project_name)
303
303
  raise ArgumentError, 'Provide nil project_name' if T.unsafe(project_name).nil?
304
304
 
@@ -72,7 +72,7 @@ module Checkoff
72
72
  def subtasks_by_gid(task_gid,
73
73
  extra_fields: [],
74
74
  only_uncompleted: true)
75
- # @type [Hash]
75
+ # @type [Hash{Symbol => undefined}]
76
76
  all_options = projects.task_options(extra_fields: extra_fields + %w[is_rendered_as_separator],
77
77
  only_uncompleted:)
78
78
  options = all_options.fetch(:options, {})
@@ -90,7 +90,6 @@ module Checkoff
90
90
  #
91
91
  # @param subtask [Asana::Resources::Task]
92
92
  # @return [Boolean, nil]
93
- # @sg-ignore Checkoff::Subtasks#subtask_section? return type could not be inferred
94
93
  def subtask_section?(subtask)
95
94
  subtask.is_rendered_as_separator
96
95
  end
data/lib/checkoff/tags.rb CHANGED
@@ -79,7 +79,6 @@ module Checkoff
79
79
  # @param tag_name [String]
80
80
  #
81
81
  # @return [Asana::Resources::Tag]
82
- # @sg-ignore nil check above is not flow-sensitive
83
82
  def tag_or_raise(workspace_name, tag_name)
84
83
  t = tag(workspace_name, tag_name)
85
84
 
@@ -113,9 +112,9 @@ module Checkoff
113
112
  #
114
113
  # @return [Hash{Symbol => Object}]
115
114
  def build_params(options)
116
- { limit: options[:per_page], completed_since: options[:completed_since] }.reject do |_, v|
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
115
+ # @type [Hash{Symbol => Object}]
116
+ params = { limit: options[:per_page], completed_since: options[:completed_since] }
117
+ params.reject do |_, v|
119
118
  v.nil? || Array(v).empty?
120
119
  end
121
120
  end