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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6f30a4354aedf2688699c3c460f2495445ce9cc647e0da109586f9be561caf4
4
- data.tar.gz: 159ce5f7020c4ed9d0c3bd2f6841e3da1c5e5fc3009a2efefc04af8ccc3b97b1
3
+ metadata.gz: cacf5e2fbe9fab040accd8cd51804fcf3f07cf0e6f050c93ecbe2da2c930bdc5
4
+ data.tar.gz: ebfe6f20e1be062a918afac5a4703df7a0d5205c69cb8d0479d5f6427c467767
5
5
  SHA512:
6
- metadata.gz: 473fa677a861c4d4d8d2d6b9c93feeaa4d3a3cd6d029298a3a8ac98bf1ec443316c08860e20f64406aa3775642cf8b08115163513b8b634fe6ddf9dddb8ae78e
7
- data.tar.gz: c50cd2149b2465e59e6a84b7ab53ca5027c8fb24aceb1e01731a39dfa52e5e52964f8ee2a38bb3b69ded267a9f989c7a9cc806bbcd2b24ad2b75be70c6d25ad6
6
+ metadata.gz: cddee9806d41d98908eca0331b3b52ee84b45968e419aad7b8817003389418f65e9536b02c2db20b2368796b447046b7093819b094ba01dcd7c59875eb404562
7
+ data.tar.gz: 82b6db32f7d6ad50d287da1fba1a537468f94aa283d60358fcf4a03953df86c41dc00da2dd514cfdbd838bd5ce2209f11e97966f00814e27fe1640cf8ad41cb3
@@ -54,7 +54,7 @@ module Checkoff
54
54
  end
55
55
 
56
56
  # @param url [String]
57
- # @param resource [Asana::Resources::Resource]
57
+ # @param resource [Asana::Resources::Task]
58
58
  # @param attachment_name [String,nil]
59
59
  # @param just_the_url [Boolean]
60
60
  # @param verify_mode [Integer] - e.g., OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER
@@ -79,15 +79,15 @@ module Checkoff
79
79
  # extension as the URL using Net::HTTP, raising an exception if
80
80
  # not succesful
81
81
  #
82
- # @param uri [URI]
82
+ # @param uri [URI::Generic]
83
83
  # @param verify_mode [Integer] - e.g., OpenSSL::SSL::VERIFY_NONE,OpenSSL::SSL::VERIFY_PEER
84
84
  #
85
85
  # @return [Object]
86
86
  def download_uri(uri, verify_mode: OpenSSL::SSL::VERIFY_PEER, &block)
87
87
  out = nil
88
- # @sg-ignore
89
88
  Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', verify_mode:) do |http|
90
- # @sg-ignore
89
+ # @sg-ignore Unresolved constant Net::HTTP::Get / Unresolved call to request —
90
+ # stdlib RBS gap on Net::HTTP block param types
91
91
  http.request(Net::HTTP::Get.new(uri)) do |response|
92
92
  raise("Unexpected response code: #{response.code}") unless response.code == '200'
93
93
 
@@ -120,7 +120,7 @@ module Checkoff
120
120
  end
121
121
 
122
122
  # @param url [String]
123
- # @param resource [Asana::Resources::Resource]
123
+ # @param resource [Asana::Resources::Task]
124
124
  # @param attachment_name [String,nil]
125
125
  # @param verify_mode [Integer] - e.g., OpenSSL::SSL::VERIFY_NONE,OpenSSL::SSL::VERIFY_PEER
126
126
  #
@@ -130,25 +130,25 @@ module Checkoff
130
130
  uri = URI(url)
131
131
  attachment_name ||= File.basename(uri.path)
132
132
  download_uri(uri, verify_mode:) do |tempfile|
133
- # @sg-ignore
133
+ # @sg-ignore attachment_name reassignment above isn't narrowed from the declared
134
+ # [String, nil] param type — needs a fresh typed local, deferred to a follow-up code PR
134
135
  content_type ||= content_type_from_filename(attachment_name)
135
- # @sg-ignore
136
+ # @sg-ignore URI::Generic#path can be nil
136
137
  content_type ||= content_type_from_filename(uri.path)
137
138
 
138
- # @sg-ignore
139
+ # @sg-ignore same attachment_name/content_type narrowing gap as above
139
140
  resource.attach(filename: attachment_name, mime: content_type,
140
141
  io: tempfile)
141
142
  end
142
143
  end
143
144
 
144
145
  # @param url [String]
145
- # @param resource [Asana::Resources::Resource]
146
+ # @param resource [Asana::Resources::Task]
146
147
  # @param attachment_name [String,nil]
147
148
  #
148
149
  # @return [Asana::Resources::Attachment]
149
150
  def create_attachment_from_url_alone!(url, resource, attachment_name:)
150
151
  with_params = {
151
- # @sg-ignore
152
152
  'parent' => resource.gid,
153
153
  'url' => url,
154
154
  'resource_subtype' => 'external',
@@ -196,7 +196,8 @@ module Checkoff
196
196
  tasks = Checkoff::Tasks.new
197
197
  attachments = Checkoff::Attachments.new
198
198
  task = tasks.task_by_gid(gid)
199
- # @sg-ignore
199
+ # @sg-ignore task_by_gid can return nil; needs a raise-if-nil guard, deferred to a
200
+ # follow-up code PR
200
201
  attachment = attachments.create_attachment_from_url!(url, task)
201
202
  puts "Results: #{attachment.inspect}"
202
203
  end
data/lib/checkoff/cli.rb CHANGED
@@ -29,7 +29,6 @@ module Checkoff
29
29
  end
30
30
 
31
31
  @from_workspace_name = from_workspace_arg
32
- # @sg-ignore
33
32
  @from_project_name = project_arg_to_name(from_project_arg)
34
33
  @from_section_name = from_section_arg
35
34
  end
@@ -41,7 +40,6 @@ module Checkoff
41
40
  if to_project_arg == :source_project
42
41
  from_project_name
43
42
  else
44
- # @sg-ignore
45
43
  project_arg_to_name(to_project_arg)
46
44
  end
47
45
  end
@@ -146,7 +144,7 @@ module Checkoff
146
144
  :to_workspace_name, :to_project_name, :to_section_name,
147
145
  :projects, :sections
148
146
 
149
- # @param project_arg [String]
147
+ # @param project_arg [Symbol, String]
150
148
  # @return [Symbol, String]
151
149
  def project_arg_to_name(project_arg)
152
150
  arg = project_arg.to_s
@@ -194,7 +192,7 @@ module Checkoff
194
192
  elsif task_name.nil?
195
193
  run_on_section(workspace_name, project_name, section_name)
196
194
  else
197
- # @sg-ignore
195
+ # @sg-ignore task_name.nil? above isn't inferred as narrowing the task_name accessor's return
198
196
  run_on_task(workspace_name, project_name, section_name, task_name)
199
197
  end
200
198
  end
@@ -218,7 +216,6 @@ module Checkoff
218
216
  # @return [String]
219
217
  def run_on_project(workspace, project)
220
218
  tasks_by_section =
221
- # @sg-ignore
222
219
  sections.tasks_by_section(workspace, project)
223
220
  tasks_by_section.update(tasks_by_section) do |_key, tasks|
224
221
  tasks_to_hash(tasks)
@@ -315,7 +312,7 @@ module Checkoff
315
312
  arg 'workspace'
316
313
  arg 'task_name'
317
314
  command :quickadd do |c|
318
- # @sg-ignore
315
+ # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
319
316
  c.action do |_global_options, _options, args|
320
317
  workspace_name = args.fetch(0)
321
318
  task_name = args.fetch(1)
@@ -330,7 +327,7 @@ module Checkoff
330
327
  arg 'section', :optional
331
328
  arg 'task_name', :optional
332
329
  command :view do |c|
333
- # @sg-ignore
330
+ # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
334
331
  c.action do |_global_options, _options, args|
335
332
  workspace_name = args.fetch(0)
336
333
  project_name = args.fetch(1)
@@ -345,37 +342,37 @@ module Checkoff
345
342
 
346
343
  # rubocop:disable Metrics/BlockLength
347
344
  command :mv do |c|
348
- # @sg-ignore
345
+ # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
349
346
  c.flag :from_workspace,
350
347
  type: String,
351
348
  default_value: :default_workspace,
352
349
  desc: 'Workspace to move tasks from'
353
- # @sg-ignore
350
+ # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
354
351
  c.flag :from_project,
355
352
  type: String,
356
353
  required: true,
357
354
  desc: 'Project to move tasks from'
358
- # @sg-ignore
355
+ # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
359
356
  c.flag :from_section,
360
357
  type: String,
361
358
  default_value: :all_sections,
362
359
  desc: 'Section to move tasks from'
363
- # @sg-ignore
360
+ # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
364
361
  c.flag :to_workspace,
365
362
  type: String,
366
363
  default_value: :source_workspace,
367
364
  desc: 'Workspace to move tasks to'
368
- # @sg-ignore
365
+ # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
369
366
  c.flag :to_project,
370
367
  type: String,
371
368
  default_value: :source_project,
372
369
  desc: 'Section to move tasks to'
373
- # @sg-ignore
370
+ # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
374
371
  c.flag :to_section,
375
372
  type: String,
376
373
  default_value: :source_section,
377
374
  desc: 'Section to move tasks to'
378
- # @sg-ignore
375
+ # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
379
376
  c.action do |_global_options, options, _args|
380
377
  from_workspace = options.fetch('from_workspace')
381
378
  from_project = options.fetch('from_project')
@@ -30,9 +30,9 @@ module Checkoff
30
30
  # @return [Asana::Client]
31
31
  def client
32
32
  @client ||= @asana_client_class.new do |c|
33
- # @sg-ignore
33
+ # @sg-ignore Unresolved call to authentication — gem config DSL block, not covered by annotations
34
34
  c.authentication :access_token, @config.fetch(:personal_access_token)
35
- # @sg-ignore
35
+ # @sg-ignore Unresolved call to default_headers — gem config DSL block, not covered by annotations
36
36
  c.default_headers 'asana-enable' =>
37
37
  'new_project_templates,new_user_task_lists,new_memberships,new_goal_memberships'
38
38
  end
@@ -41,7 +41,7 @@ module Checkoff
41
41
  # @param custom_field_name [String]
42
42
  #
43
43
  # @return [Asana::Resources::CustomField]
44
- # @sg-ignore
44
+ # @sg-ignore nil check above is not flow-sensitive
45
45
  def custom_field_or_raise(workspace_name, custom_field_name)
46
46
  cf = custom_field(workspace_name, custom_field_name)
47
47
  raise "Could not find custom_field #{custom_field_name} under workspace #{workspace_name}." if cf.nil?
@@ -78,8 +78,10 @@ module Checkoff
78
78
 
79
79
  # @param resource [Asana::Resources::Project,Asana::Resources::Task]
80
80
  # @param custom_field_name [String]
81
- # @sg-ignore
82
81
  # @return [Array<String>]
82
+ # @sg-ignore Declared return type Array<String> does not match inferred type Array,Array<Array> —
83
+ # fixing cleanly needs binding enum_value.fetch('name') to a typed local; leaving for a
84
+ # follow-up code PR to keep this one annotation-only.
83
85
  def resource_custom_field_values_names_by_name(resource, custom_field_name)
84
86
  custom_field = resource_custom_field_by_name(resource, custom_field_name)
85
87
  return [] if custom_field.nil?
@@ -108,9 +110,9 @@ module Checkoff
108
110
  end
109
111
 
110
112
  # @param resource [Asana::Resources::Task,Asana::Resources::Project]
111
- # @sg-ignore
112
113
  # @param custom_field_name [String]
113
114
  # @return [Hash]
115
+ # @sg-ignore nil check above is not flow-sensitive
114
116
  def resource_custom_field_by_name_or_raise(resource, custom_field_name)
115
117
  custom_field = resource_custom_field_by_name(resource, custom_field_name)
116
118
  if custom_field.nil?
@@ -120,10 +122,10 @@ module Checkoff
120
122
  custom_field
121
123
  end
122
124
 
123
- # @sg-ignore
124
125
  # @param resource [Asana::Resources::Project,Asana::Resources::Task]
125
126
  # @param custom_field_gid [String]
126
127
  # @return [Hash]
128
+ # @sg-ignore nil check above is not flow-sensitive
127
129
  def resource_custom_field_by_gid_or_raise(resource, custom_field_gid)
128
130
  # @type [Array<Hash>]
129
131
  custom_fields = resource.custom_fields
@@ -146,15 +148,15 @@ module Checkoff
146
148
  # @param custom_field [Hash{String => Hash,Array<Hash>}]
147
149
  #
148
150
  # @return [Array<Hash>]
149
- # @sg-ignore
151
+ # @sg-ignore Declared return type Array<Hash> does not match inferred type Array,Hash,Array<Hash> —
152
+ # fixing cleanly needs restructuring the case/when to bind each branch to a typed local; leaving
153
+ # for a follow-up code PR to keep this one annotation-only.
150
154
  def resource_custom_field_enum_values(custom_field)
151
155
  resource_subtype = custom_field.fetch('resource_subtype')
152
156
  case resource_subtype
153
157
  when 'enum'
154
- # @type [Array<Hash>]
155
158
  [custom_field.fetch('enum_value')]
156
159
  when 'multi_enum'
157
- # @type [Array<Hash>]
158
160
  custom_field.fetch('multi_enum_values')
159
161
  else
160
162
  raise "Teach me how to handle resource_subtype #{resource_subtype}"
@@ -74,9 +74,11 @@ module Checkoff
74
74
  end
75
75
  resource = webhook_subscription&.fetch('resource', nil)
76
76
  name, resource_type = enrich_gid(resource) if resource
77
- # @sg-ignore
77
+ # @sg-ignore webhook_subscription is declared [Hash, nil] but this line assumes non-nil
78
+ # without the &. used elsewhere in this method — looks like a real nil-safety gap, not
79
+ # just a typing gap; flagging for the follow-up code PR rather than silently widening
78
80
  webhook_subscription['checkoff:enriched:name'] = name if name
79
- # @sg-ignore
81
+ # @sg-ignore same nil-safety gap as above
80
82
  webhook_subscription['checkoff:enriched:resource_type'] = resource_type if resource_type
81
83
  end
82
84
 
@@ -96,13 +98,11 @@ module Checkoff
96
98
 
97
99
  # @param filter [Hash{String => String, Array<String>}]
98
100
  #
99
- # @sg-ignore
100
- # @return [String, nil]
101
+ # @return [void]
101
102
  def enrich_filter_parent_gid!(filter)
102
- parent_gid = filter['checkoff:parent.gid']
103
+ parent_gid = T.cast(filter['checkoff:parent.gid'], T.nilable(String))
103
104
  return unless parent_gid
104
105
 
105
- # @sg-ignore
106
106
  name, resource_type = enrich_gid(parent_gid)
107
107
  filter['checkoff:enriched:parent.name'] = name if name
108
108
  filter['checkoff:enriched:parent.resource_type'] = resource_type if resource_type
@@ -112,11 +112,10 @@ module Checkoff
112
112
  #
113
113
  # @return [void]
114
114
  def enrich_filter_resource!(filter)
115
- resource_gid = filter['checkoff:resource.gid']
115
+ resource_gid = T.cast(filter['checkoff:resource.gid'], T.nilable(String))
116
116
 
117
117
  return unless resource_gid
118
118
 
119
- # @sg-ignore
120
119
  task = tasks.task_by_gid(resource_gid)
121
120
  task_name = task&.name
122
121
  filter['checkoff:enriched:resource.name'] = task_name if task_name
@@ -134,19 +133,22 @@ module Checkoff
134
133
  filter['checkoff:enriched:fetched.section.name'] = name if name
135
134
  end
136
135
 
137
- # @param asana_event [Hash{'resource' => Hash}]
136
+ # @param asana_event [Hash]
138
137
  #
139
138
  # @return [void]
140
139
  def enrich_event_parent!(asana_event)
141
140
  # @type [Hash{String => String }]
142
- # @sg-ignore
143
141
  parent = asana_event['parent']
144
142
 
145
143
  return unless parent
146
144
 
147
145
  # @type [String]
146
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
147
+ # https://github.com/castwide/solargraph/pull/1228
148
148
  resource_type = parent.fetch('resource_type')
149
149
  # @type [String]
150
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
151
+ # https://github.com/castwide/solargraph/pull/1228
150
152
  gid = parent.fetch('gid')
151
153
  name, _resource_type = enrich_gid(gid, resource_type:)
152
154
  parent['checkoff:enriched:name'] = name if name
@@ -154,16 +156,20 @@ module Checkoff
154
156
  nil
155
157
  end
156
158
 
157
- # @param asana_event [Hash{'resource' => Hash}]
159
+ # @param asana_event [Hash]
158
160
  #
159
161
  # @return [void]
160
162
  def enrich_event_resource!(asana_event)
161
163
  # @type [Hash{String => String}]
162
164
  resource = T.cast(asana_event['resource'], T::Hash[String, String])
163
165
  # @type [String]
166
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
167
+ # https://github.com/castwide/solargraph/pull/1228
164
168
  resource_type = resource.fetch('resource_type')
165
169
 
166
170
  # @type [String]
171
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
172
+ # https://github.com/castwide/solargraph/pull/1228
167
173
  gid = resource.fetch('gid')
168
174
 
169
175
  name, _resource_type = enrich_gid(gid, resource_type:)
@@ -122,6 +122,8 @@ module Checkoff
122
122
  # @type [Hash{String => String}]
123
123
  resource = asana_event.fetch('resource')
124
124
  # @type [String]
125
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
126
+ # https://github.com/castwide/solargraph/pull/1228
125
127
  resource_type = resource.fetch('resource_type')
126
128
  unless resource_type == 'task'
127
129
  raise "Teach me how to check #{key.inspect} on resource type #{resource_type.inspect}"
@@ -131,6 +133,8 @@ module Checkoff
131
133
  options = {
132
134
  fields:,
133
135
  }
136
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
137
+ # https://github.com/castwide/solargraph/pull/1228
134
138
  @client.tasks.find_by_id(task_gid, options:)
135
139
  rescue Asana::Errors::NotFound
136
140
  nil
@@ -20,12 +20,12 @@ module Checkoff
20
20
 
21
21
  # @param key [Symbol]
22
22
  # @return [Object]
23
- # @sg-ignore
23
+ # @sg-ignore return type could not be inferred — cascades from the ENV.fetch RBS gap below
24
24
  def [](key)
25
25
  config_value = @config[key]
26
26
  return config_value unless config_value.nil?
27
27
 
28
- # @sg-ignore
28
+ # @sg-ignore Unresolved call to fetch on RBS::Unnamed::ENVClass, Class<ENV>
29
29
  ENV.fetch(envvar_name(key), nil)
30
30
  end
31
31
 
@@ -57,10 +57,10 @@ module Logging
57
57
  private
58
58
 
59
59
  # @return [Symbol]
60
- # @sg-ignore
60
+ # @sg-ignore return type could not be inferred — cascades from the ENV.fetch RBS gap below
61
61
  def log_level
62
- # @sg-ignore
63
62
  # rubocop:disable Style/RedundantFetchBlock
63
+ # @sg-ignore Unresolved call to fetch on RBS::Unnamed::ENVClass, Class<ENV>
64
64
  ENV.fetch('LOG_LEVEL') { 'INFO' }.downcase.to_sym
65
65
  # rubocop:enable Style/RedundantFetchBlock
66
66
  end
@@ -59,19 +59,20 @@ module Checkoff
59
59
  # @param gid [String]
60
60
  # @param single_custom_field_params [Hash{String => Array<String>}]
61
61
  # @return [Array(Hash{String => String}, Array<Symbol, Array>)]
62
- # @sg-ignore
62
+ # @sg-ignore `unless variant_class.nil?` return value isn't inferred as the declared tuple type
63
63
  def convert_single_custom_field_params(gid, single_custom_field_params)
64
64
  variant_key = "custom_field_#{gid}.variant"
65
65
  variant = single_custom_field_params.fetch(variant_key)
66
66
  remaining_params = single_custom_field_params.reject { |k, _v| k == variant_key }
67
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
68
+ # https://github.com/castwide/solargraph/pull/1228
67
69
  raise "Teach me how to handle #{variant_key} = #{variant}" unless variant.length == 1
68
70
 
69
- # @sg-ignore
70
- # @type [Class<CustomFieldVariant>]
71
- # @sg-ignore
71
+ # @type [Class<CustomFieldVariant>, nil]
72
+ # @sg-ignore variant.length == 1 guard above isn't inferred as narrowing variant[0] to non-nil
72
73
  variant_class = VARIANTS[variant[0]]
73
74
  # @type [Array(Hash{String => String}, Array<Symbol, Array>)]
74
- # @sg-ignore
75
+ # @sg-ignore `unless variant_class.nil?` isn't inferred as narrowing the receiver in the same statement
75
76
  return variant_class.new(gid, remaining_params).convert unless variant_class.nil?
76
77
 
77
78
  raise "Teach me how to handle #{variant_key} = #{variant}"
@@ -79,9 +80,9 @@ module Checkoff
79
80
 
80
81
  # @param key [String]
81
82
  # @return [String]
82
- # @sg-ignore
83
+ # @sg-ignore split(...)[n]'s nilable Array indexing isn't inferred as the declared String return
83
84
  def gid_from_custom_field_key(key)
84
- # @sg-ignore
85
+ # @sg-ignore split(...)[n] is nilable Array indexing; key's shape guarantees enough parts here
85
86
  key.split('_')[2].split('.')[0]
86
87
  end
87
88
 
@@ -37,6 +37,8 @@ module Checkoff
37
37
  case remaining_params.keys
38
38
  when [param_name]
39
39
  # @type param_values [Array<String>]
40
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
41
+ # https://github.com/castwide/solargraph/pull/1228
40
42
  param_values = remaining_params.fetch(param_name)
41
43
  unless param_values.length == 1
42
44
  raise "Teach me how to handle these remaining keys for #{param_name}: #{remaining_params}"
@@ -15,7 +15,7 @@ module Checkoff
15
15
  end
16
16
 
17
17
  # @return [Array(Hash{String => String}, Array<Symbol, Array>)]
18
- # @sg-ignore
18
+ # @sg-ignore out can genuinely be nil if none of the 3 known prefixes matched; relies on the raise above it
19
19
  def convert
20
20
  return [{}, []] if date_url_params.empty?
21
21
 
@@ -129,15 +129,19 @@ module Checkoff
129
129
  end
130
130
 
131
131
  # @param param_key [String]
132
- # @sg-ignore
133
132
  # @return [String]
133
+ # @sg-ignore value.length == 1 guard above isn't inferred as narrowing value[0] to non-nil
134
134
  def get_single_param(param_key)
135
135
  raise "Expected #{param_key} to have at least one value" unless date_url_params.key? param_key
136
136
 
137
137
  value = date_url_params.fetch(param_key)
138
138
 
139
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
140
+ # https://github.com/castwide/solargraph/pull/1228
139
141
  raise "Expected #{param_key} to have one value" if value.length != 1
140
142
 
143
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
144
+ # https://github.com/castwide/solargraph/pull/1228
141
145
  value[0]
142
146
  end
143
147
 
@@ -154,6 +158,8 @@ module Checkoff
154
158
  def validate_unit_is_day!(prefix)
155
159
  unit = date_url_params.fetch("#{prefix}.unit").fetch(0)
156
160
 
161
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
162
+ # https://github.com/castwide/solargraph/pull/1228
157
163
  raise "Teach me how to handle other time units: #{unit}" unless unit == 'day'
158
164
  end
159
165
 
@@ -24,9 +24,10 @@ module Checkoff
24
24
  # @param url [String]
25
25
  # @return [Array(Hash{String => String}, Array)]
26
26
  def convert_params(url)
27
- # @sg-ignore
27
+ # @sg-ignore URI#query can be nil for a URL with no query string; CGI.parse expects
28
+ # String — looks like a real gap (unhandled no-query-string case), not just a typing
29
+ # gap, flagging for the follow-up code PR rather than silently asserting non-nil
28
30
  url_params = CGI.parse(URI.parse(url).query)
29
- # @sg-ignore
30
31
  custom_field_params, date_url_params, simple_url_params = partition_url_params(url_params)
31
32
  custom_field_args, custom_field_task_selector = convert_custom_field_params(custom_field_params)
32
33
  date_url_args, date_task_selector = convert_date_params(date_url_params)
@@ -56,14 +57,15 @@ module Checkoff
56
57
  CustomFieldParamConverter.new(custom_field_params:).convert
57
58
  end
58
59
 
59
- # @param url_params [Hash{String => String}]
60
+ # @param url_params [Hash{String => Array<String>}]
60
61
  # @return [Array(Hash{String => Array<String>}, Hash{String => Array<String>}, Hash{String => Array<String>})]
61
62
  def partition_url_params(url_params)
62
63
  groups = T.let(url_params.to_a.group_by do |key, _values|
63
- # @sg-ignore
64
+ # @sg-ignore Unresolved call to start_with? — T.let wrapping this
65
+ # chained group_by block seems to block block-param inference
64
66
  if key.start_with? 'custom_field_'
65
67
  :custom_field
66
- # @sg-ignore
68
+ # @sg-ignore Unresolved call to include? — same T.let/block-param gap
67
69
  elsif key.include? '_date'
68
70
  :date
69
71
  else
@@ -17,8 +17,7 @@ module Checkoff
17
17
  end
18
18
 
19
19
  # @param task_selectors [Array<Symbol, Array>]
20
- # @return [Array(Symbol, Array, Array)]
21
- # @sg-ignore
20
+ # @return [Symbol, Array, Array(Symbol, Array, Array)]
22
21
  def self.merge_task_selectors(*task_selectors)
23
22
  return [] if task_selectors.empty?
24
23
 
@@ -31,6 +31,16 @@ module Checkoff
31
31
  end
32
32
  end
33
33
 
34
+ public
35
+
36
+ # @return [Array<String>]
37
+ # @sg-ignore abstract method always raises; declared type documents the override contract, not this body
38
+ def convert
39
+ raise 'Implement me!'
40
+ end
41
+
42
+ private
43
+
34
44
  # @return [String]
35
45
  attr_reader :key
36
46
 
@@ -226,7 +236,6 @@ module Checkoff
226
236
  # @return [Hash{String => String}] the converted params
227
237
  def convert
228
238
  # @type [Array<Array(String, String)>]
229
- # @sg-ignore
230
239
  arr_of_tuples = simple_url_params.to_a.flat_map do |key, values|
231
240
  # @type
232
241
  entry = convert_arg(key, values).each_slice(2).to_a
@@ -265,14 +274,14 @@ module Checkoff
265
274
  # https://developers.asana.com/docs/search-tasks-in-a-workspace
266
275
  # @param key [String] the name of the search url param
267
276
  # @param values [Array<String>] the values of the search url param
268
- # @sg-ignore
269
- # @return [Hash{String => String}] the converted params
277
+ # @return [Array<String>] the converted params, as an alternating key/value flat array
270
278
  def convert_arg(key, values)
271
279
  # @type [Class<SimpleParam::SimpleParam>]
280
+ # @sg-ignore Hash#fetch generic<X> leak on rbs >= 4.1.0, fix in progress upstream
281
+ # https://github.com/castwide/solargraph/pull/1228
272
282
  clazz = ARGS.fetch(key)
273
283
  # @type [SimpleParam::SimpleParam]
274
284
  obj = clazz.new(key:, values:)
275
- # @sg-ignore
276
285
  obj.convert
277
286
  end
278
287
 
@@ -17,6 +17,9 @@ module Checkoff
17
17
  super()
18
18
  end
19
19
 
20
+ # @return [Checkoff::CustomFields]
21
+ attr_reader :custom_fields
22
+
20
23
  private
21
24
 
22
25
  # @return [Array(Symbol, Array), String]