checkoff 0.258.0 → 0.260.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 +5 -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 +92 -146
  47. data/sig/checkoff.rbs +79 -153
  48. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cacf5e2fbe9fab040accd8cd51804fcf3f07cf0e6f050c93ecbe2da2c930bdc5
4
- data.tar.gz: ebfe6f20e1be062a918afac5a4703df7a0d5205c69cb8d0479d5f6427c467767
3
+ metadata.gz: e08117848a46a27ffee42a7936ada4febefdda2c2d9a44ae0160dcdfa89989b8
4
+ data.tar.gz: a8573c8961ab241c73425008c5c9a44f03c6038a4a3ad791b3154b505cd4ee22
5
5
  SHA512:
6
- metadata.gz: cddee9806d41d98908eca0331b3b52ee84b45968e419aad7b8817003389418f65e9536b02c2db20b2368796b447046b7093819b094ba01dcd7c59875eb404562
7
- data.tar.gz: 82b6db32f7d6ad50d287da1fba1a537468f94aa283d60358fcf4a03953df86c41dc00da2dd514cfdbd838bd5ce2209f11e97966f00814e27fe1640cf8ad41cb3
6
+ metadata.gz: 43b8c4f4ff4d1fcd48a98d0fe2b7fdc6332ab29e83b81f2dad3a9a9ec3628f3c106c4ea518ae81b0e9edc882bf9618396b547c099e6dc1fb22f76d5c91ed6cd5
7
+ data.tar.gz: 2da763b8482d3e7a6f7e8fe8b912ef2c8175d6843da3019fc10a7aae9ea94d3978a9ed789763f5fee8d7755200f80bd04268b332ab74b2865fd6303823c99e34
@@ -85,9 +85,10 @@ module Checkoff
85
85
  # @return [Object]
86
86
  def download_uri(uri, verify_mode: OpenSSL::SSL::VERIFY_PEER, &block)
87
87
  out = nil
88
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', verify_mode:) do |http|
89
- # @sg-ignore Unresolved constant Net::HTTP::Get / Unresolved call to request
90
- # stdlib RBS gap on Net::HTTP block param types
88
+ host = uri.host || raise("URI has no host: #{uri}")
89
+ Net::HTTP.start(host, uri.port, use_ssl: uri.scheme == 'https', verify_mode:) do |http|
90
+ # @sg-ignore tool-limitation:pr-1290-follow-up
91
+ # https://github.com/castwide/solargraph/pull/1290#issuecomment-5272957964
91
92
  http.request(Net::HTTP::Get.new(uri)) do |response|
92
93
  raise("Unexpected response code: #{response.code}") unless response.code == '200'
93
94
 
@@ -108,7 +109,6 @@ module Checkoff
108
109
  def write_tempfile_from_response(response)
109
110
  Tempfile.create('checkoff') do |tempfile|
110
111
  tempfile.binmode
111
- # @sg-ignore Unresolved call to read_body on #read_body
112
112
  response.read_body do |chunk|
113
113
  tempfile.write(chunk)
114
114
  end
@@ -128,16 +128,15 @@ module Checkoff
128
128
  def create_attachment_from_downloaded_url!(url, resource, attachment_name:,
129
129
  verify_mode: OpenSSL::SSL::VERIFY_PEER)
130
130
  uri = URI(url)
131
- attachment_name ||= File.basename(uri.path)
131
+ # @type [String]
132
+ resolved_attachment_name = attachment_name || File.basename(uri.path || '')
132
133
  download_uri(uri, verify_mode:) do |tempfile|
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
135
- content_type ||= content_type_from_filename(attachment_name)
136
- # @sg-ignore URI::Generic#path can be nil
137
- content_type ||= content_type_from_filename(uri.path)
138
-
139
- # @sg-ignore same attachment_name/content_type narrowing gap as above
140
- resource.attach(filename: attachment_name, mime: content_type,
134
+ content_type = content_type_from_filename(resolved_attachment_name)
135
+ content_type ||= content_type_from_filename(uri.path || '')
136
+ # neither the attachment name nor the URL's path has a MIME-recognizable extension
137
+ content_type ||= 'application/octet-stream'
138
+
139
+ resource.attach(filename: resolved_attachment_name, mime: content_type,
141
140
  io: tempfile)
142
141
  end
143
142
  end
@@ -196,8 +195,8 @@ module Checkoff
196
195
  tasks = Checkoff::Tasks.new
197
196
  attachments = Checkoff::Attachments.new
198
197
  task = tasks.task_by_gid(gid)
199
- # @sg-ignore task_by_gid can return nil; needs a raise-if-nil guard, deferred to a
200
- # follow-up code PR
198
+ raise "Could not find task #{gid}" if task.nil?
199
+
201
200
  attachment = attachments.create_attachment_from_url!(url, task)
202
201
  puts "Results: #{attachment.inspect}"
203
202
  end
data/lib/checkoff/cli.rb CHANGED
@@ -161,7 +161,7 @@ module Checkoff
161
161
  # @param workspace_name [String]
162
162
  # @param project_name [String, Symbol]
163
163
  # @param section_name [String, Symbol, nil]
164
- # @param task_name [String, Symbol, nil]
164
+ # @param task_name [String, nil]
165
165
  # @param config [Checkoff::Internal::EnvFallbackConfigLoader, Hash]
166
166
  # @param projects [Checkoff::Projects]
167
167
  # @param sections [Checkoff::Sections]
@@ -192,7 +192,6 @@ module Checkoff
192
192
  elsif task_name.nil?
193
193
  run_on_section(workspace_name, project_name, section_name)
194
194
  else
195
- # @sg-ignore task_name.nil? above isn't inferred as narrowing the task_name accessor's return
196
195
  run_on_task(workspace_name, project_name, section_name, task_name)
197
196
  end
198
197
  end
@@ -243,7 +242,6 @@ module Checkoff
243
242
  task = tasks.task(workspace.to_s, project, task_name, section_name: section)
244
243
  raise "Task not found: #{task_name}" if task.nil?
245
244
 
246
- # @sg-ignore nil check above is not flow-sensitive
247
245
  task_to_hash(task).to_json
248
246
  end
249
247
 
@@ -311,8 +309,9 @@ module Checkoff
311
309
  desc 'Add a short task to Asana'
312
310
  arg 'workspace'
313
311
  arg 'task_name'
312
+ # @param c [GLI::Command]
314
313
  command :quickadd do |c|
315
- # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
314
+ # @param args [Array<String>]
316
315
  c.action do |_global_options, _options, args|
317
316
  workspace_name = args.fetch(0)
318
317
  task_name = args.fetch(1)
@@ -326,8 +325,9 @@ module Checkoff
326
325
  arg 'project'
327
326
  arg 'section', :optional
328
327
  arg 'task_name', :optional
328
+ # @param c [GLI::Command]
329
329
  command :view do |c|
330
- # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
330
+ # @param args [Array<String>]
331
331
  c.action do |_global_options, _options, args|
332
332
  workspace_name = args.fetch(0)
333
333
  project_name = args.fetch(1)
@@ -341,38 +341,33 @@ module Checkoff
341
341
  desc 'Move tasks from one section to another within a project'
342
342
 
343
343
  # rubocop:disable Metrics/BlockLength
344
+ # @param c [GLI::Command]
344
345
  command :mv do |c|
345
- # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
346
346
  c.flag :from_workspace,
347
347
  type: String,
348
348
  default_value: :default_workspace,
349
349
  desc: 'Workspace to move tasks from'
350
- # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
351
350
  c.flag :from_project,
352
351
  type: String,
353
352
  required: true,
354
353
  desc: 'Project to move tasks from'
355
- # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
356
354
  c.flag :from_section,
357
355
  type: String,
358
356
  default_value: :all_sections,
359
357
  desc: 'Section to move tasks from'
360
- # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
361
358
  c.flag :to_workspace,
362
359
  type: String,
363
360
  default_value: :source_workspace,
364
361
  desc: 'Workspace to move tasks to'
365
- # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
366
362
  c.flag :to_project,
367
363
  type: String,
368
364
  default_value: :source_project,
369
365
  desc: 'Section to move tasks to'
370
- # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
371
366
  c.flag :to_section,
372
367
  type: String,
373
368
  default_value: :source_section,
374
369
  desc: 'Section to move tasks to'
375
- # @sg-ignore GLI's DSL block methods (c.flag/c.action) aren't resolved
370
+ # @param options [Hash]
376
371
  c.action do |_global_options, options, _args|
377
372
  from_workspace = options.fetch('from_workspace')
378
373
  from_project = options.fetch('from_project')
@@ -30,9 +30,7 @@ module Checkoff
30
30
  # @return [Asana::Client]
31
31
  def client
32
32
  @client ||= @asana_client_class.new do |c|
33
- # @sg-ignore Unresolved call to authentication — gem config DSL block, not covered by annotations
34
33
  c.authentication :access_token, @config.fetch(:personal_access_token)
35
- # @sg-ignore Unresolved call to default_headers — gem config DSL block, not covered by annotations
36
34
  c.default_headers 'asana-enable' =>
37
35
  'new_project_templates,new_user_task_lists,new_memberships,new_goal_memberships'
38
36
  end
@@ -41,7 +41,6 @@ module Checkoff
41
41
  # @param custom_field_name [String]
42
42
  #
43
43
  # @return [Asana::Resources::CustomField]
44
- # @sg-ignore nil check above is not flow-sensitive
45
44
  def custom_field_or_raise(workspace_name, custom_field_name)
46
45
  cf = custom_field(workspace_name, custom_field_name)
47
46
  raise "Could not find custom_field #{custom_field_name} under workspace #{workspace_name}." if cf.nil?
@@ -79,40 +78,32 @@ module Checkoff
79
78
  # @param resource [Asana::Resources::Project,Asana::Resources::Task]
80
79
  # @param custom_field_name [String]
81
80
  # @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.
85
81
  def resource_custom_field_values_names_by_name(resource, custom_field_name)
86
82
  custom_field = resource_custom_field_by_name(resource, custom_field_name)
87
83
  return [] if custom_field.nil?
88
84
 
89
85
  resource_custom_field_enum_values(custom_field).flat_map do |enum_value|
90
- if enum_value.nil?
91
- []
92
- else
93
- [enum_value.fetch('name')]
94
- end
86
+ [enum_value.fetch('name')]
95
87
  end
96
88
  end
97
89
 
98
90
  # @param resource [Asana::Resources::Task,Asana::Resources::Project]
99
91
  # @param custom_field_name [String]
100
- # @return [Hash, nil]
92
+ # @return [Hash{String => Object}, nil]
101
93
  def resource_custom_field_by_name(resource, custom_field_name)
102
- # @type [Array<Hash>]
94
+ # @type [Array<Hash{String => Object}>]
103
95
  custom_fields = resource.custom_fields
104
96
  if custom_fields.nil?
105
97
  raise "custom fields not found on resource - did you add 'custom_fields' in your extra_fields argument?"
106
98
  end
107
99
 
108
- # @type [Hash, nil]
100
+ # @type [Hash{String => Object}, nil]
109
101
  custom_fields.find { |field| field.fetch('name') == custom_field_name }
110
102
  end
111
103
 
112
104
  # @param resource [Asana::Resources::Task,Asana::Resources::Project]
113
105
  # @param custom_field_name [String]
114
- # @return [Hash]
115
- # @sg-ignore nil check above is not flow-sensitive
106
+ # @return [Hash{String => Object}]
116
107
  def resource_custom_field_by_name_or_raise(resource, custom_field_name)
117
108
  custom_field = resource_custom_field_by_name(resource, custom_field_name)
118
109
  if custom_field.nil?
@@ -124,16 +115,15 @@ module Checkoff
124
115
 
125
116
  # @param resource [Asana::Resources::Project,Asana::Resources::Task]
126
117
  # @param custom_field_gid [String]
127
- # @return [Hash]
128
- # @sg-ignore nil check above is not flow-sensitive
118
+ # @return [Hash{String => Object}]
129
119
  def resource_custom_field_by_gid_or_raise(resource, custom_field_gid)
130
- # @type [Array<Hash>]
120
+ # @type [Array<Hash{String => Object}>]
131
121
  custom_fields = resource.custom_fields
132
122
  if custom_fields.nil?
133
123
  raise "Could not find custom_fields under project (was 'custom_fields' included in 'extra_fields'?)"
134
124
  end
135
125
 
136
- # @type [Hash, nil]
126
+ # @type [Hash{String => Object}, nil]
137
127
  matched_custom_field = custom_fields.find { |data| data.fetch('gid') == custom_field_gid }
138
128
  if matched_custom_field.nil?
139
129
  raise "Could not find custom field with gid #{custom_field_gid} " \
@@ -145,12 +135,11 @@ module Checkoff
145
135
 
146
136
  private
147
137
 
148
- # @param custom_field [Hash{String => Hash,Array<Hash>}]
138
+ # rubocop:disable Layout/LineLength, YARD/TagTypeSyntax
139
+ # @param custom_field [Hash{"resource_subtype" => String} & Hash{"enum_value" => Hash} & Hash{"multi_enum_values" => Array<Hash>}]
149
140
  #
150
- # @return [Array<Hash>]
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.
141
+ # @return [Array<Hash{"gid" => String} & Hash{"name" => String}>]
142
+ # rubocop:enable Layout/LineLength, YARD/TagTypeSyntax
154
143
  def resource_custom_field_enum_values(custom_field)
155
144
  resource_subtype = custom_field.fetch('resource_subtype')
156
145
  case resource_subtype
@@ -163,7 +152,7 @@ module Checkoff
163
152
  end
164
153
  end
165
154
 
166
- # @param custom_field [Hash]
155
+ # @param custom_field [Hash{String => Object}]
167
156
  # @param enum_value [Hash{String => String}, nil]
168
157
  # @return [Array<String>]
169
158
  def find_gids(custom_field, enum_value)
@@ -69,16 +69,14 @@ module Checkoff
69
69
  #
70
70
  # @return [void]
71
71
  def enrich_webhook_subscription!(webhook_subscription)
72
- webhook_subscription&.fetch('filters', nil)&.map! do |filter|
72
+ return if webhook_subscription.nil?
73
+
74
+ webhook_subscription.fetch('filters', nil)&.map! do |filter|
73
75
  enrich_filter(filter)
74
76
  end
75
- resource = webhook_subscription&.fetch('resource', nil)
77
+ resource = webhook_subscription.fetch('resource', nil)
76
78
  name, resource_type = enrich_gid(resource) if resource
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
80
79
  webhook_subscription['checkoff:enriched:name'] = name if name
81
- # @sg-ignore same nil-safety gap as above
82
80
  webhook_subscription['checkoff:enriched:resource_type'] = resource_type if resource_type
83
81
  end
84
82
 
@@ -100,8 +98,8 @@ module Checkoff
100
98
  #
101
99
  # @return [void]
102
100
  def enrich_filter_parent_gid!(filter)
103
- parent_gid = T.cast(filter['checkoff:parent.gid'], T.nilable(String))
104
- return unless parent_gid
101
+ parent_gid = filter['checkoff:parent.gid']
102
+ return unless parent_gid.is_a?(String)
105
103
 
106
104
  name, resource_type = enrich_gid(parent_gid)
107
105
  filter['checkoff:enriched:parent.name'] = name if name
@@ -112,9 +110,9 @@ module Checkoff
112
110
  #
113
111
  # @return [void]
114
112
  def enrich_filter_resource!(filter)
115
- resource_gid = T.cast(filter['checkoff:resource.gid'], T.nilable(String))
113
+ resource_gid = filter['checkoff:resource.gid']
116
114
 
117
- return unless resource_gid
115
+ return unless resource_gid.is_a?(String)
118
116
 
119
117
  task = tasks.task_by_gid(resource_gid)
120
118
  task_name = task&.name
@@ -125,8 +123,8 @@ module Checkoff
125
123
  #
126
124
  # @return [void]
127
125
  def enrich_filter_section!(filter)
128
- section_gid = T.cast(filter['checkoff:fetched.section.gid'], T.nilable(String))
129
- return unless section_gid
126
+ section_gid = filter['checkoff:fetched.section.gid']
127
+ return unless section_gid.is_a?(String)
130
128
 
131
129
  section = sections.section_by_gid(section_gid)
132
130
  name = section&.name
@@ -143,12 +141,8 @@ module Checkoff
143
141
  return unless parent
144
142
 
145
143
  # @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
144
  resource_type = parent.fetch('resource_type')
149
145
  # @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
152
146
  gid = parent.fetch('gid')
153
147
  name, _resource_type = enrich_gid(gid, resource_type:)
154
148
  parent['checkoff:enriched:name'] = name if name
@@ -161,15 +155,11 @@ module Checkoff
161
155
  # @return [void]
162
156
  def enrich_event_resource!(asana_event)
163
157
  # @type [Hash{String => String}]
164
- resource = T.cast(asana_event['resource'], T::Hash[String, String])
158
+ resource = asana_event['resource']
165
159
  # @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
168
160
  resource_type = resource.fetch('resource_type')
169
161
 
170
162
  # @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
173
163
  gid = resource.fetch('gid')
174
164
 
175
165
  name, _resource_type = enrich_gid(gid, resource_type:)
@@ -52,7 +52,7 @@ module Checkoff
52
52
 
53
53
  private
54
54
 
55
- # @param filter [Hash]
55
+ # @param filter [Hash{String => String, Array<String>}]
56
56
  # @param asana_event [Hash]
57
57
  # @param failures [Array<String>]
58
58
  #
@@ -122,8 +122,6 @@ 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
127
125
  resource_type = resource.fetch('resource_type')
128
126
  unless resource_type == 'task'
129
127
  raise "Teach me how to check #{key.inspect} on resource type #{resource_type.inspect}"
@@ -133,8 +131,6 @@ module Checkoff
133
131
  options = {
134
132
  fields:,
135
133
  }
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
138
134
  @client.tasks.find_by_id(task_gid, options:)
139
135
  rescue Asana::Errors::NotFound
140
136
  nil
@@ -20,12 +20,10 @@ module Checkoff
20
20
 
21
21
  # @param key [Symbol]
22
22
  # @return [Object]
23
- # @sg-ignore return type could not be inferred — cascades from the ENV.fetch RBS gap below
24
23
  def [](key)
25
24
  config_value = @config[key]
26
25
  return config_value unless config_value.nil?
27
26
 
28
- # @sg-ignore Unresolved call to fetch on RBS::Unnamed::ENVClass, Class<ENV>
29
27
  ENV.fetch(envvar_name(key), nil)
30
28
  end
31
29
 
@@ -57,10 +57,8 @@ module Logging
57
57
  private
58
58
 
59
59
  # @return [Symbol]
60
- # @sg-ignore return type could not be inferred — cascades from the ENV.fetch RBS gap below
61
60
  def log_level
62
61
  # rubocop:disable Style/RedundantFetchBlock
63
- # @sg-ignore Unresolved call to fetch on RBS::Unnamed::ENVClass, Class<ENV>
64
62
  ENV.fetch('LOG_LEVEL') { 'INFO' }.downcase.to_sym
65
63
  # rubocop:enable Style/RedundantFetchBlock
66
64
  end
@@ -10,9 +10,10 @@ module Checkoff
10
10
  def initialize(_deps = {}); end
11
11
 
12
12
  # @param project_obj [Asana::Resources::Project]
13
- # @param project [String, Symbol] - :not_specified, :my_tasks
13
+ # @param project [String, :not_specified, :my_tasks, nil] - a String is a
14
+ # project name
14
15
  #
15
- # @return [Hash]
16
+ # @return [Hash{String => Object}]
16
17
  def project_to_h(project_obj, project: :not_specified)
17
18
  project = project_obj.name if project == :not_specified
18
19
  project_hash = { **project_obj.to_h, 'project' => project }
@@ -26,8 +27,8 @@ module Checkoff
26
27
  # @param project_hash [Hash]
27
28
  # @return [void]
28
29
  def unwrap_custom_fields(project_hash)
29
- # @type [Array<Hash>,nil]
30
- custom_fields = project_hash.fetch('custom_fields', nil)
30
+ # @type [Array<Hash{String => Object}>,nil]
31
+ custom_fields = project_hash['custom_fields']
31
32
 
32
33
  return if custom_fields.nil?
33
34
 
@@ -41,7 +41,7 @@ module Checkoff
41
41
  end.transform_values(&:to_h)
42
42
  end
43
43
 
44
- # @type [Hash{String => Class<CustomFieldVariant>}]
44
+ # @type [Hash{String => Class<CustomFieldVariant::CustomFieldVariant>}]
45
45
  VARIANTS = {
46
46
  'is' => CustomFieldVariant::Is,
47
47
  'no_value' => CustomFieldVariant::NoValue,
@@ -59,20 +59,15 @@ 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 `unless variant_class.nil?` return value isn't inferred as the declared tuple type
63
62
  def convert_single_custom_field_params(gid, single_custom_field_params)
64
63
  variant_key = "custom_field_#{gid}.variant"
65
64
  variant = single_custom_field_params.fetch(variant_key)
66
65
  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
69
66
  raise "Teach me how to handle #{variant_key} = #{variant}" unless variant.length == 1
70
67
 
71
- # @type [Class<CustomFieldVariant>, nil]
72
- # @sg-ignore variant.length == 1 guard above isn't inferred as narrowing variant[0] to non-nil
68
+ # @type [Class<CustomFieldVariant::CustomFieldVariant>, nil]
73
69
  variant_class = VARIANTS[variant[0]]
74
70
  # @type [Array(Hash{String => String}, Array<Symbol, Array>)]
75
- # @sg-ignore `unless variant_class.nil?` isn't inferred as narrowing the receiver in the same statement
76
71
  return variant_class.new(gid, remaining_params).convert unless variant_class.nil?
77
72
 
78
73
  raise "Teach me how to handle #{variant_key} = #{variant}"
@@ -80,10 +75,14 @@ module Checkoff
80
75
 
81
76
  # @param key [String]
82
77
  # @return [String]
83
- # @sg-ignore split(...)[n]'s nilable Array indexing isn't inferred as the declared String return
84
78
  def gid_from_custom_field_key(key)
85
- # @sg-ignore split(...)[n] is nilable Array indexing; key's shape guarantees enough parts here
86
- key.split('_')[2].split('.')[0]
79
+ gid_and_suffix = key.split('_')[2]
80
+ raise "Unexpected custom field param key: #{key}" if gid_and_suffix.nil?
81
+
82
+ gid = gid_and_suffix.split('.')[0]
83
+ raise "Unexpected custom field param key: #{key}" if gid.nil?
84
+
85
+ gid
87
86
  end
88
87
 
89
88
  # @return [Hash{String => Array<String>}]
@@ -15,6 +15,13 @@ module Checkoff
15
15
  @remaining_params = T.let(remaining_params, T::Hash[String, T::Array[String]])
16
16
  end
17
17
 
18
+ # Implemented by every subclass.
19
+ #
20
+ # @return [Array(Hash{String => String}, Array<Symbol, Array>)]
21
+ def convert
22
+ raise NotImplementedError
23
+ end
24
+
18
25
  private
19
26
 
20
27
  # @return [String]
@@ -37,8 +44,6 @@ module Checkoff
37
44
  case remaining_params.keys
38
45
  when [param_name]
39
46
  # @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
42
47
  param_values = remaining_params.fetch(param_name)
43
48
  unless param_values.length == 1
44
49
  raise "Teach me how to handle these remaining keys for #{param_name}: #{remaining_params}"
@@ -15,7 +15,6 @@ module Checkoff
15
15
  end
16
16
 
17
17
  # @return [Array(Hash{String => String}, Array<Symbol, Array>)]
18
- # @sg-ignore out can genuinely be nil if none of the 3 known prefixes matched; relies on the raise above it
19
18
  def convert
20
19
  return [{}, []] if date_url_params.empty?
21
20
 
@@ -30,11 +29,19 @@ module Checkoff
30
29
 
31
30
  raise "Teach me to handle these parameters: #{date_url_params.inspect}" unless date_url_params.empty?
32
31
 
33
- out
32
+ ensure_matched!(out)
34
33
  end
35
34
 
36
35
  private
37
36
 
37
+ # @param out [Array(Hash{String => String}, Array<Symbol, Array>), nil]
38
+ # @return [Array(Hash{String => String}, Array<Symbol, Array>)]
39
+ def ensure_matched!(out)
40
+ return out if out
41
+
42
+ raise "no date param matched a known prefix: #{date_url_params.inspect}"
43
+ end
44
+
38
45
  # @param prefix [String]
39
46
  # @return [Array(Hash{String => String}, Array<Symbol, Array>)]
40
47
  def convert_for_prefix(prefix)
@@ -130,19 +137,17 @@ module Checkoff
130
137
 
131
138
  # @param param_key [String]
132
139
  # @return [String]
133
- # @sg-ignore value.length == 1 guard above isn't inferred as narrowing value[0] to non-nil
134
140
  def get_single_param(param_key)
135
141
  raise "Expected #{param_key} to have at least one value" unless date_url_params.key? param_key
136
142
 
137
143
  value = date_url_params.fetch(param_key)
138
144
 
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
141
145
  raise "Expected #{param_key} to have one value" if value.length != 1
142
146
 
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
145
- value[0]
147
+ single_value = value[0]
148
+ raise "Expected #{param_key} to have a non-nil value" if single_value.nil?
149
+
150
+ single_value
146
151
  end
147
152
 
148
153
  # @param prefix [String]
@@ -158,8 +163,6 @@ module Checkoff
158
163
  def validate_unit_is_day!(prefix)
159
164
  unit = date_url_params.fetch("#{prefix}.unit").fetch(0)
160
165
 
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
163
166
  raise "Teach me how to handle other time units: #{unit}" unless unit == 'day'
164
167
  end
165
168
 
@@ -24,10 +24,8 @@ module Checkoff
24
24
  # @param url [String]
25
25
  # @return [Array(Hash{String => String}, Array)]
26
26
  def convert_params(url)
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
30
- url_params = CGI.parse(URI.parse(url).query)
27
+ # URI#query is nil for a URL with no query string; CGI.parse('') correctly yields {}
28
+ url_params = CGI.parse(URI.parse(url).query || '')
31
29
  custom_field_params, date_url_params, simple_url_params = partition_url_params(url_params)
32
30
  custom_field_args, custom_field_task_selector = convert_custom_field_params(custom_field_params)
33
31
  date_url_args, date_task_selector = convert_date_params(date_url_params)
@@ -61,11 +59,8 @@ module Checkoff
61
59
  # @return [Array(Hash{String => Array<String>}, Hash{String => Array<String>}, Hash{String => Array<String>})]
62
60
  def partition_url_params(url_params)
63
61
  groups = T.let(url_params.to_a.group_by do |key, _values|
64
- # @sg-ignore Unresolved call to start_with? — T.let wrapping this
65
- # chained group_by block seems to block block-param inference
66
62
  if key.start_with? 'custom_field_'
67
63
  :custom_field
68
- # @sg-ignore Unresolved call to include? — same T.let/block-param gap
69
64
  elsif key.include? '_date'
70
65
  :date
71
66
  else
@@ -34,7 +34,6 @@ module Checkoff
34
34
  public
35
35
 
36
36
  # @return [Array<String>]
37
- # @sg-ignore abstract method always raises; declared type documents the override contract, not this body
38
37
  def convert
39
38
  raise 'Implement me!'
40
39
  end
@@ -242,7 +241,7 @@ module Checkoff
242
241
  entry
243
242
  end
244
243
  # @type [Hash{String => String}]
245
- out = T.cast(arr_of_tuples.to_h, T::Hash[String, String])
244
+ out = arr_of_tuples.to_h
246
245
  unless out.include? 'sort_by'
247
246
  # keep results consistent between calls; API using default
248
247
  # sort_by does not seem to be.
@@ -277,8 +276,6 @@ module Checkoff
277
276
  # @return [Array<String>] the converted params, as an alternating key/value flat array
278
277
  def convert_arg(key, values)
279
278
  # @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
282
279
  clazz = ARGS.fetch(key)
283
280
  # @type [SimpleParam::SimpleParam]
284
281
  obj = clazz.new(key:, values:)
@@ -53,7 +53,7 @@ module Checkoff
53
53
  { sections:, projects:, client:, custom_fields: }
54
54
  end
55
55
 
56
- # @return [Asana::Resources::Project]
56
+ # @return [Asana::Resources::Section]
57
57
  attr_reader :item
58
58
  # @return [Checkoff::Sections]
59
59
  attr_reader :sections
@@ -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