steep 2.0.0 → 2.1.0.dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c7bef651d95df5415538e6d1a288b29395b2e49bdbabe20f3a79c51669760f7
4
- data.tar.gz: a60d0fddf36b2e962d8c02802ab9f24424a2a428b07e63b5eb6c40c744fc0e71
3
+ metadata.gz: 9985b9a834986497b037bbe836f6646aad30fcc1dfa33cf187a3f6aea572d5c6
4
+ data.tar.gz: a3ef68064767f01a511e48b02598f4dffa6c403554898b59a76762a732aaa448
5
5
  SHA512:
6
- metadata.gz: 66f4985fee2186d7e9a4166b8a54f34272c3d156df4fc315d56ae4246361ba7fb44a77ebd8cf0f85860abe4bc75f5b5be5fad3c36a6183be2a4060cdbdacbc7a
7
- data.tar.gz: ccf6e2b72945cb200fe448532887750f1b89e08a8ad3926cdc50711164aced9fb9fec693a0981825d604592889f46f3cb4bc94e8c23e437e368955e5187c4c8d
6
+ metadata.gz: '018023551867526507d0b4a490a871c7b06e7bce17aedc87a1e6622d21fc5de212530fd34b94b4fc81fe45e618312433c5a4426969a27bd2144a9a84ee40c874'
7
+ data.tar.gz: 5d5c55850108f163260d34f0745e684efa15c922d22a9b417ac41272757c78b579656229ecf191d8e14e33b39cae015cc8fc876d8f4ccdd1fba99045e467a725
data/README.md CHANGED
@@ -247,7 +247,7 @@ Use `bundle exec rake -T` to see all available tasks.
247
247
 
248
248
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
249
249
 
250
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
250
+ To install this gem onto your local machine, run `bundle exec rake install`. Releases are cut by the `Release gem` workflow rather than from a working copy -- see [doc/release.md](doc/release.md).
251
251
 
252
252
  ## Contributing
253
253
 
data/Rakefile CHANGED
@@ -29,210 +29,413 @@ namespace :test do
29
29
  end
30
30
  end
31
31
 
32
- Rake::Task[:release].enhance do
33
- Rake::Task[:"release:note"].invoke
34
- Rake::Task[:"release:github"].invoke
35
- Rake::Task[:"release:release-prs"].invoke
36
- end
32
+ # Pull requests with one of these labels are omitted from the changelog.
33
+ CHANGELOG_SKIP_LABELS = ["skip-changelog"]
34
+
35
+ # The tags a release proper starts *after*, rather than at.
36
+ CHANGELOG_PRERELEASE_TAGS = ["v*.pre*", "v*.dev*"]
37
+
38
+ # Resolves the commit-ish the changelog of the release being prepared starts from.
39
+ #
40
+ # `version` is a version number, a tag, or any commit-ish. When it is omitted, the base follows
41
+ # `Steep::VERSION`:
42
+ #
43
+ # * `X.Y.Z.pre.N` documents what changed since `X.Y.Z.pre.N-1`, so it starts from the latest tag.
44
+ # * `X.Y.Z` documents the whole cycle, the prereleases included, so it skips the prerelease tags
45
+ # in between and starts from the previous release proper.
46
+ #
47
+ # This is the step that is easy to get wrong by hand: on a release proper the latest tag is a
48
+ # prerelease, so the obvious default would produce only the tail of the cycle. Passing a version
49
+ # explicitly overrides all of it.
50
+ #
51
+ def changelog_base(version)
52
+ require "open3"
37
53
 
38
- desc "Generate changelog template from GH pull requests"
39
- task :changelog do
40
- major, minor, patch, pre = Steep::VERSION.split(".", 4)
41
- major = major.to_i
42
- minor = minor.to_i
43
- patch = patch.to_i
54
+ from =
55
+ if version
56
+ # `2.1.0` and `v2.1.0` both mean the tag `v2.1.0`, while `master` or a SHA is used as is.
57
+ version.match?(/\A\d/) ? "v#{version}" : version
58
+ else
59
+ command = ["git", "describe", "--tags", "--match", "v*", "--abbrev=0"]
60
+ unless Gem::Version.new(Steep::VERSION).prerelease?
61
+ CHANGELOG_PRERELEASE_TAGS.each { |glob| command.push("--exclude", glob) }
62
+ end
44
63
 
45
- if patch == 0
46
- milestone = "Steep #{major}.#{minor}"
47
- else
48
- milestone = "Steep #{major}.#{minor}.x"
49
- end
64
+ output, status = Open3.capture2(*command)
65
+ raise "🚨 Cannot detect the tag the changelog starts from. Give the previous version explicitly." unless status.success?
66
+ output.chomp
67
+ end
50
68
 
51
- puts "🔍 Finding pull requests that is associated to milestone `#{milestone}`..."
69
+ _, status = Open3.capture2("git", "rev-parse", "--verify", "--quiet", "#{from}^{commit}")
70
+ raise "🚨 No such commit-ish: `#{from}`" unless status.success?
52
71
 
53
- command = [
54
- "gh",
55
- "pr",
56
- "list",
57
- "--json",
58
- "url,title,number",
59
- "--limit=100",
60
- "--search" ,
61
- "milestone:\"#{milestone}\" is:merged sort:updated-desc -label:Released"
62
- ]
72
+ from
73
+ end
63
74
 
75
+ # Runs a GraphQL query against the repository of the working directory.
76
+ #
77
+ # `body` is the selection set inside `repository`, so a query can use the `$owner` and `$name`
78
+ # variables. Returns the contents of `data.repository`.
79
+ #
80
+ def changelog_graphql(body)
64
81
  require "open3"
65
- output, status = Open3.capture2(*command)
66
- raise status.inspect unless status.success?
67
-
68
82
  require "json"
69
- json = JSON.parse(output, symbolize_names: true)
70
83
 
71
- unless json.empty?
72
- puts
73
- json.each do |line|
74
- puts "* #{line[:title]} ([##{line[:number]}](#{line[:url]}))"
84
+ @changelog_repository ||=
85
+ begin
86
+ output, status = Open3.capture2("gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner")
87
+ raise status.inspect unless status.success?
88
+ output.chomp.split("/", 2)
75
89
  end
76
- else
77
- puts " (🤑 There is no *unreleased* pull request associated to the milestone.)"
78
- end
90
+ owner, name = @changelog_repository
91
+
92
+ query = <<~GRAPHQL
93
+ query($owner: String!, $name: String!) {
94
+ repository(owner: $owner, name: $name) {
95
+ #{body}
96
+ }
97
+ }
98
+ GRAPHQL
99
+
100
+ output, status = Open3.capture2(
101
+ "gh", "api", "graphql",
102
+ "-f", "query=#{query}",
103
+ "-f", "owner=#{owner}",
104
+ "-f", "name=#{name}",
105
+ binmode: true
106
+ )
107
+ raise status.inspect unless status.success?
108
+
109
+ # GitHub always answers in UTF-8, while the default external encoding follows the locale. Without
110
+ # this, a pull request body with an emoji fails to parse under `LANG=C`, as in GitHub Actions.
111
+ JSON.parse(output.force_encoding(Encoding::UTF_8), symbolize_names: true).dig(:data, :repository)
79
112
  end
80
113
 
81
- namespace :release do
82
- desc "Ensure release note"
83
- task :note do
84
- version = Gem::Version.new(Steep::VERSION)
85
- major, minor, patch, pre = Steep::VERSION.split(".", 4)
86
- major = major.to_i
87
- minor = minor.to_i
88
- patch = patch.to_i
114
+ # Lists the commits between `from` and `HEAD`, newest first.
115
+ #
116
+ def changelog_commits(from)
117
+ require "open3"
89
118
 
90
- wiki_url = "https://github.com/soutaro/steep/wiki/Release-Note-#{major}.#{minor}"
119
+ output, status = Open3.capture2("git", "log", "--format=%H", "#{from}..HEAD")
120
+ raise status.inspect unless status.success?
91
121
 
92
- puts "🧐 Checking if Release note page already exists..."
122
+ output.lines.map(&:chomp).reject(&:empty?)
123
+ end
93
124
 
94
- unless `curl --silent -o/dev/null --head #{wiki_url} -w '%{http_code}'` == "200"
95
- pre_flag = version.prerelease? ? " --pre" : ""
96
- pre_requirement = version.prerelease? ? ", '~> #{major}.#{minor}.#{patch}.#{pre}'" : ""
125
+ # What `git cherry-pick -x` appends to the message of the commit it creates.
126
+ CHERRY_PICK_ORIGIN = /^\(cherry picked from commit ([0-9a-f]{40})\)$/
127
+
128
+ # Maps the commits that record where they were cherry-picked from to that commit.
129
+ #
130
+ # A backport is a cherry-pick, so on a release branch it is the recorded origin, not the commit
131
+ # itself, that leads to the pull request the change was written and reviewed in. Without this a
132
+ # backported change is attributed to the pull request that carried the backport, which says
133
+ # nothing about the change and is the same for every commit it brought over.
134
+ #
135
+ # A commit backported twice -- the development line, then a release branch -- carries one line
136
+ # per hop, appended in order, so the first one is where the change started.
137
+ #
138
+ def changelog_origins(commits)
139
+ return {} if commits.empty?
97
140
 
98
- puts "----"
141
+ require "open3"
99
142
 
100
- puts <<~PREFIX if patch
101
- **The latest version of Steep #{major}.#{minor} is `#{pre}`.**
143
+ # `--no-walk` prints these commits and nothing else. NUL delimiters keep a commit message --
144
+ # which can contain anything, including what this format looks like -- from being read as the
145
+ # format itself.
146
+ output, status = Open3.capture2("git", "log", "--no-walk", "--format=%H%x00%B%x00", *commits, binmode: true)
147
+ raise status.inspect unless status.success?
102
148
 
103
- PREFIX
149
+ # Commit messages are UTF-8, while the default external encoding follows the locale. Without
150
+ # this, splitting a message that is not ASCII fails under `LANG=C`, as in GitHub Actions.
151
+ output.force_encoding(Encoding::UTF_8)
104
152
 
105
- puts <<~TEMPLATE
106
- Some of the highlights in Steep #{major}.#{minor} are:
153
+ output.split("\0").each_slice(2).each_with_object({}) do |(commit, message), origins|
154
+ commit = commit.to_s.strip
155
+ next if commit.empty?
107
156
 
108
- * New feature 1 (URL)
109
- * New feature 2 (URL)
110
- * New feature 3 (URL)
157
+ origin = message.to_s[CHERRY_PICK_ORIGIN, 1] or next
158
+ origins[commit] = origin
159
+ end
160
+ end
111
161
 
112
- You can install it with `$ gem install steep#{pre_flag}` or using Bundler.
162
+ # Asks GitHub which pull requests each commit came from, so that any merge strategy -- merge
163
+ # commit, squash, or rebase -- is handled without parsing commit messages.
164
+ #
165
+ # Returns `{ oid => [pull request, ...] }`, with an empty array for the commits GitHub has no
166
+ # merged pull request for, including the ones it does not know at all.
167
+ #
168
+ def changelog_associated_pull_requests(oids)
169
+ oids.uniq.each_slice(50).each_with_object({}) do |slice, found|
170
+ aliases = slice.map.with_index do |oid, index|
171
+ <<~GRAPHQL
172
+ c#{index}: object(oid: "#{oid}") {
173
+ ... on Commit {
174
+ associatedPullRequests(first: 10) {
175
+ nodes {
176
+ number title url merged
177
+ labels(first: 100) { nodes { name } }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ GRAPHQL
183
+ end
113
184
 
114
- ```rb
115
- gem 'steep', require: false#{pre_requirement}
116
- ```
185
+ response = changelog_graphql(aliases.join("\n"))
117
186
 
118
- See the [CHANGELOG](https://github.com/soutaro/steep/blob/master/CHANGELOG.md) for the details.
187
+ slice.each_with_index do |oid, index|
188
+ nodes = response.dig(:"c#{index}", :associatedPullRequests, :nodes) || []
119
189
 
120
- ## New feature 1
190
+ found[oid] = nodes.select { |pr| pr[:merged] }.map do |pr|
191
+ { number: pr[:number], title: pr[:title], url: pr[:url], labels: pr.dig(:labels, :nodes).map { |label| label[:name] } }
192
+ end
193
+ end
194
+ end
195
+ end
121
196
 
122
- ## New feature 2
197
+ # Finds the pull requests the commits came from, keeping the order of `commits`.
198
+ #
199
+ # Returns the pull requests for the changelog and the ones omitted by `skip_labels`.
200
+ #
201
+ def changelog_pull_requests(commits, skip_labels: CHANGELOG_SKIP_LABELS)
202
+ origins = changelog_origins(commits)
203
+ found = changelog_associated_pull_requests(commits.map { |commit| origins[commit] || commit })
204
+
205
+ # An origin that leads nowhere -- a commit cherry-picked from a fork, or one that went to the
206
+ # default branch without a pull request -- falls back to the commit in this history, which is
207
+ # at least the backport that brought it here.
208
+ fallbacks = commits.select { |commit| origins[commit] && found.fetch(origins[commit], []).empty? }
209
+ found.update(changelog_associated_pull_requests(fallbacks)) unless fallbacks.empty?
210
+
211
+ pull_requests = {}
212
+ skipped = {}
213
+
214
+ commits.each do |commit|
215
+ prs = found.fetch(origins[commit] || commit, [])
216
+ prs = found.fetch(commit, []) if prs.empty?
217
+
218
+ prs.each do |pr|
219
+ if (pr[:labels] & skip_labels).empty?
220
+ pull_requests[pr[:number]] ||= pr
221
+ else
222
+ skipped[pr[:number]] ||= pr
223
+ end
224
+ end
225
+ end
123
226
 
124
- ## New feature 3
227
+ [pull_requests.values, skipped.values]
228
+ end
125
229
 
126
- ## Diagnostics updates
230
+ # Fetches the details that help classifying the pull requests: the changed files and the body.
231
+ #
232
+ def changelog_pull_request_details(pull_requests)
233
+ pull_requests.each_slice(50).flat_map do |slice|
234
+ aliases = slice.map do |pr|
235
+ <<~GRAPHQL
236
+ p#{pr[:number]}: pullRequest(number: #{pr[:number]}) {
237
+ body
238
+ author { login }
239
+ files(first: 100) {
240
+ nodes { path }
241
+ pageInfo { hasNextPage }
242
+ }
243
+ }
244
+ GRAPHQL
245
+ end
127
246
 
128
- ## Updating Steep
247
+ details = changelog_graphql(aliases.join("\n"))
129
248
 
130
- TEMPLATE
131
- puts "----"
132
- puts
249
+ slice.map do |pr|
250
+ detail = details[:"p#{pr[:number]}"] or next pr
133
251
 
134
- puts " ⏩️ Create the release note with the template: #{wiki_url}"
135
- else
136
- if patch == 0 || version.prerelease?
137
- puts " ⏩️ Open the release note and update it at: #{wiki_url}"
138
- else
139
- puts " ✅ Release note is ready!"
140
- end
252
+ pr.merge(
253
+ author: detail.dig(:author, :login),
254
+ # The body is a hint for writing the changelog, not a copy source. Keep it short.
255
+ body: detail[:body].to_s.strip.slice(0, 1000),
256
+ files: detail.dig(:files, :nodes).map { |file| file[:path] },
257
+ files_truncated: detail.dig(:files, :pageInfo, :hasNextPage)
258
+ )
141
259
  end
142
- puts
143
260
  end
261
+ end
144
262
 
145
- desc "Create GitHub release automatically"
146
- task :github do
147
- version = Gem::Version.new(Steep::VERSION)
148
- major, minor, patch, *_ = Steep::VERSION.split(".")
149
- major = major.to_i
150
- minor = minor.to_i
151
- patch = patch.to_i
263
+ # Reports the pull requests omitted by their label, so that they do not disappear silently.
264
+ #
265
+ def warn_skipped_pull_requests(skipped, skip_labels)
266
+ return if skipped.empty?
152
267
 
153
- puts "✏️ Making a draft release on GitHub..."
268
+ numbers = skipped.map { |pr| "##{pr[:number]}" }
269
+ numbers = numbers.take(20).push("and #{numbers.size - 20} more") if numbers.size > 20
154
270
 
155
- content = File.read(File.join(__dir__, "CHANGELOG.md"))
156
- changelog = content.scan(/^## \d.*?(?=^## \d)/m)[0]
157
- changelog = changelog.sub(/^.*\n^.*\n/, "").rstrip
271
+ $stderr.puts
272
+ $stderr.puts " (⏭️ Skipped #{skipped.size} pull request(s) labeled #{skip_labels.map { |label| "`#{label}`" }.join(" or ")}: #{numbers.join(", ")})"
273
+ end
158
274
 
159
- notes = <<NOTES
160
- [Release note](https://github.com/soutaro/steep/wiki/Release-Note-#{major}.#{minor})
275
+ # Prints the changelog template listing the pull requests merged between `from` and `HEAD`.
276
+ #
277
+ # The changelog goes to STDOUT and everything else goes to STDERR, so that the output can be
278
+ # piped to another command: `rake gem:changelog | pbcopy`
279
+ #
280
+ def print_changelog(from, skip_labels: CHANGELOG_SKIP_LABELS)
281
+ $stderr.puts "🔍 Finding pull requests merged between `#{from}` and `HEAD`..."
282
+
283
+ commits = changelog_commits(from)
284
+ if commits.empty?
285
+ $stderr.puts " (🤔 There is no commit after `#{from}`.)"
286
+ return
287
+ end
161
288
 
162
- #{changelog}
163
- NOTES
289
+ pull_requests, skipped = changelog_pull_requests(commits, skip_labels: skip_labels)
164
290
 
165
- command = [
166
- "gh",
167
- "release",
168
- "create",
169
- "--draft",
170
- "v#{Steep::VERSION}",
171
- "--title=#{Steep::VERSION}",
172
- "--notes=#{notes}"
173
- ]
174
-
175
- if version.prerelease?
176
- command << "--prerelease"
291
+ if pull_requests.empty?
292
+ $stderr.puts " (🤔 No pull request is associated to the commits after `#{from}`.)"
293
+ else
294
+ $stderr.puts
295
+ pull_requests.each do |pr|
296
+ puts "* #{pr[:title]} ([##{pr[:number]}](#{pr[:url]}))"
177
297
  end
298
+ $stdout.flush
299
+ end
178
300
 
179
- require "open3"
180
- output, status = Open3.capture2(*command)
181
- if status.success?
182
- puts " ⏩️ Done! Open #{output.chomp} and publish the release!"
183
- puts
301
+ warn_skipped_pull_requests(skipped, skip_labels)
302
+ end
303
+
304
+ # Prints the same pull requests as `print_changelog` as JSON, with the details that help
305
+ # classifying them into the sections of CHANGELOG.md.
306
+ #
307
+ # This is the input for the release automation, so it always prints a valid JSON document.
308
+ #
309
+ def print_changelog_json(from, skip_labels: CHANGELOG_SKIP_LABELS)
310
+ require "json"
311
+
312
+ $stderr.puts "🔍 Finding pull requests merged between `#{from}` and `HEAD`..."
313
+
314
+ commits = changelog_commits(from)
315
+ pull_requests, skipped = changelog_pull_requests(commits, skip_labels: skip_labels)
316
+ pull_requests = changelog_pull_request_details(pull_requests)
317
+
318
+ $stderr.puts " (📋 #{pull_requests.size} pull request(s))"
319
+
320
+ puts JSON.pretty_generate(
321
+ {
322
+ from: from,
323
+ to: "HEAD",
324
+ pull_requests: pull_requests,
325
+ skipped: skipped
326
+ }
327
+ )
328
+ $stdout.flush
329
+
330
+ warn_skipped_pull_requests(skipped, skip_labels)
331
+ end
332
+
333
+ namespace :gem do
334
+ desc "Generate changelog template from GH pull requests merged since the previous release"
335
+ task :changelog, [:version] do |_task, args|
336
+ print_changelog(changelog_base(args[:version]))
337
+ end
338
+
339
+ namespace :changelog do
340
+ desc "Print the pull requests of `gem:changelog` as JSON, with the changed files and body of each"
341
+ task :json, [:version] do |_task, args|
342
+ print_changelog_json(changelog_base(args[:version]))
184
343
  end
185
344
  end
186
345
 
187
- desc "Add `Released` labels to pull requests associated to the version"
188
- task "release-prs" do
189
- major, minor, patch, pre = Steep::VERSION.split(".", 4)
190
- major = major.to_i
191
- minor = minor.to_i
192
- patch = patch.to_i
346
+ # There are three kinds of release: `X.Y.Z`, `X.Y.Z.pre.N`, and `X.Y.Z.dev.N`. The
347
+ # `.dev.N` ones are cut from the development line for people who need a specific
348
+ # change early; they are not written up in the changelog, so there are no notes to
349
+ # publish and nothing worth announcing.
350
+ def dev_release?(version)
351
+ Gem::Version.new(version).segments.include?("dev")
352
+ end
353
+
354
+ # The body of the topmost section of CHANGELOG.md, which is the release being
355
+ # prepared, minus its own heading.
356
+ #
357
+ # The encoding is explicit because the default external encoding follows the
358
+ # locale, and the changelog is not ASCII.
359
+ #
360
+ def changelog_section(version)
361
+ content = File.read(File.join(__dir__, "CHANGELOG.md"), encoding: Encoding::UTF_8)
362
+ section = content.scan(/^## \d.*?(?=^## \d)/m)[0] or raise "🚨 Cannot find a release section in CHANGELOG.md"
363
+ heading, _, body = section.partition("\n")
364
+ heading.include?(version) or raise "🚨 CHANGELOG.md starts with `#{heading.strip}`, which is not #{version}"
365
+ body.strip
366
+ end
193
367
 
194
- if patch == 0
195
- milestone = "Steep #{major}.#{minor}"
368
+ desc "Check that the working tree is ready to be released as the given version"
369
+ task :check_release, [:version] do |_task, args|
370
+ version = args[:version] or raise "🚨 Pass the version being released: `rake 'gem:check_release[2.1.0]'`"
371
+ Gem::Version.correct?(version) or raise "🚨 `#{version}` is not a version number."
372
+
373
+ # The version being released and the version the commit declares are stated
374
+ # separately -- one by whoever starts the release, one by the commit itself --
375
+ # so that releasing the wrong commit, or releasing the right one under the wrong
376
+ # name, fails here rather than on RubyGems.
377
+ version == Steep::VERSION or
378
+ raise "🚨 Releasing #{version}, but this commit declares `Steep::VERSION = #{Steep::VERSION.inspect}`."
379
+
380
+ if dev_release?(version)
381
+ puts "✅ #{version} is the version of this commit. It is a dev release, so CHANGELOG.md is not checked."
196
382
  else
197
- milestone = "Steep #{major}.#{minor}.x"
383
+ changelog_section(version)
384
+ puts "✅ #{version} is the version of this commit, and CHANGELOG.md documents it."
198
385
  end
386
+ end
387
+
388
+ desc "Create and push the `vX.Y.Z` tag for Steep::VERSION"
389
+ task :tag do
390
+ tag = "v#{Steep::VERSION}"
391
+
392
+ # Annotated, so that the tag carries its own author and date rather than
393
+ # borrowing the tagged commit's.
394
+ sh "git", "tag", "--annotate", "--message", "Steep #{Steep::VERSION}", tag
395
+ sh "git", "push", "origin", tag
396
+
397
+ puts "🏷️ Pushed #{tag}."
398
+ end
399
+
400
+ desc "Publish the GitHub release for Steep::VERSION, unless it is a `.dev.` version"
401
+ task :gh_release do
402
+ require "open3"
403
+
404
+ version = Gem::Version.new(Steep::VERSION)
405
+ major, minor, *_ = Steep::VERSION.split(".")
406
+ tag = "v#{Steep::VERSION}"
199
407
 
200
- if pre =~ /dev/
201
- puts "🔍 Skipping `Released` tags because `dev` release: `#{pre}`"
408
+ if dev_release?(Steep::VERSION)
409
+ puts "⏭️ #{Steep::VERSION} is a dev release, so there is no GitHub release to publish."
202
410
  next
203
411
  end
204
412
 
205
- puts "🔍 Finding pull requests that is associated to milestone `#{milestone}`..."
413
+ # The release is created against an existing tag, so that the artifacts and the
414
+ # notes describe a commit that is already immutable.
415
+ _, status = Open3.capture2("git", "rev-parse", "--verify", "--quiet", "#{tag}^{commit}")
416
+ raise "🚨 No such tag: `#{tag}`. Tag the release before creating the GitHub release." unless status.success?
206
417
 
418
+ notes = <<~NOTES
419
+ [Release note](https://github.com/soutaro/steep/wiki/Release-Note-#{major}.#{minor})
420
+
421
+ #{changelog_section(Steep::VERSION)}
422
+ NOTES
423
+
424
+ # Published rather than drafted: the notes are the changelog section that was
425
+ # already reviewed in the release pull request, so there is nothing left to edit.
207
426
  command = [
208
- "gh",
209
- "pr",
210
- "list",
211
- "--json",
212
- "url,title,number",
213
- "--search" ,
214
- "milestone:\"#{milestone}\" is:merged sort:updated-desc -label:Released"
427
+ "gh", "release", "create", tag,
428
+ "--title=#{Steep::VERSION}",
429
+ "--notes=#{notes}"
215
430
  ]
431
+ command << "--prerelease" if version.prerelease?
216
432
 
217
- require "open3"
218
433
  output, status = Open3.capture2(*command)
219
- raise status.inspect unless status.success?
434
+ raise "🚨 `gh release create` failed: #{status.inspect}" unless status.success?
220
435
 
221
- require "json"
222
- json = JSON.parse(output, symbolize_names: true)
223
-
224
- puts " ✅ Found #{json.size} PRs..."
225
-
226
- json.each do |pr|
227
- puts "🧐 Updating #{pr[:url]}..."
228
- output, status = Open3.capture2("gh", "pr", "edit", pr[:number].to_s, "--add-label", "Released")
229
- raise status.inspect unless status.success?
230
- puts " ✅ Done!"
231
- sleep 0.5
232
- end
436
+ puts "📝 Released #{tag}: #{output.chomp}"
233
437
  end
234
438
  end
235
-
236
439
  namespace :rbs do
237
440
  task :watch do
238
441
  require "listen"
data/doc/release.md ADDED
@@ -0,0 +1,264 @@
1
+ # Releasing Steep
2
+
3
+ A release is a pull request and one workflow run. Everything that leaves the
4
+ repository — the tag, the gem, and the GitHub release — is produced by the
5
+ `Release gem` workflow, so nothing has to be built or pushed from a laptop.
6
+
7
+ There are three kinds of release, and they differ in what gets written up:
8
+
9
+ | Version | CHANGELOG section | GitHub release |
10
+ | --- | --- | --- |
11
+ | `X.Y.Z` | The whole cycle since the previous release proper, prereleases included | Published |
12
+ | `X.Y.Z.pre.N` | What changed since `X.Y.Z.pre.N-1` | Published, marked as a prerelease |
13
+ | `X.Y.Z.dev.N` | None | None |
14
+
15
+ `.dev.N` releases are cut from the development line for people who need a change
16
+ early, so they are gems and tags and nothing else.
17
+
18
+ ## Prerequisites
19
+
20
+ Push rights to the `steep` gem on RubyGems are **not** needed: the workflow
21
+ authenticates through a trusted publisher registered for this repository and
22
+ `release.yml`. What is needed is write access to the repository, since that is
23
+ what lets you dispatch the workflow.
24
+
25
+ ## Steps
26
+
27
+ The release pull request in step 1 is merged by a person who has reviewed it. Its merge commit is
28
+ what step 2 dispatches, tags, and pushes to RubyGems, and none of that can be taken back — so
29
+ prepare that pull request and stop there, rather than merging it and carrying on to step 2.
30
+
31
+ The bump that starts a new minor is the only other pull request that sets `Steep::VERSION`. It
32
+ publishes nothing and another bump undoes it, so one opened on an explicit request can go through
33
+ on its own.
34
+
35
+ ### 1. Prepare the release
36
+
37
+ Open a pull request that carries everything the release needs:
38
+
39
+ - `lib/steep/version.rb` — set `Steep::VERSION` to the version being released.
40
+ - `CHANGELOG.md` — add a section for the new version, directly under the `# CHANGELOG` heading.
41
+ Sections are newest first.
42
+
43
+ Label the pull request `skip-changelog`. It carries no change of its own, and without the label it
44
+ shows up in the next release's list.
45
+
46
+ `rake gem:changelog` lists the pull requests merged since the last release, already formatted:
47
+
48
+ ```console
49
+ $ bundle exec rake gem:changelog | pbcopy
50
+ ```
51
+
52
+ Where it starts follows `Steep::VERSION`, so bump the version first: a prerelease starts from the
53
+ latest tag, and a release proper skips the prerelease tags and starts from the previous release
54
+ proper. Pass a version to override it (`rake 'gem:changelog[2.0.0]'`). Only the list goes to
55
+ STDOUT, so it pipes cleanly. Pull requests labeled `skip-changelog` are left out and reported on
56
+ STDERR.
57
+
58
+ Sort the list into the sections below. `rake gem:changelog:json` prints the same pull requests with
59
+ the changed files, labels, and body of each, which is what the sorting is based on.
60
+
61
+ Both tasks reach GitHub through `gh`, which a Claude Code on the web session cannot do. See
62
+ [Assembling the changelog without `gh`](#assembling-the-changelog-without-gh) for how the same list
63
+ is produced there.
64
+
65
+ ```markdown
66
+ ## X.Y.Z (YYYY-MM-DD)
67
+
68
+ ### Type checker core
69
+
70
+ ### Commandline tool
71
+
72
+ ### Language server
73
+
74
+ ### Miscellaneous
75
+ ```
76
+
77
+ The sections always appear in this order; delete the ones that end up empty. One thing scales with
78
+ the size of the release: **summary paragraphs**, above the first section. A patch release usually
79
+ has none, while 2.0.0 opens with a `### Summary` describing its three major features.
80
+
81
+ The date is the day the gem is released, matching the `vX.Y.Z` tag — not the day this pull request
82
+ is opened. Fix it up before step 2 if the pull request sat for a few days.
83
+
84
+ ### 2. Run the `Release gem` workflow
85
+
86
+ Once the pull request is merged, dispatch
87
+ [`release.yml`](../.github/workflows/release.yml) from the Actions tab with two inputs:
88
+
89
+ | Input | Value |
90
+ | --- | --- |
91
+ | `commit` | The full 40-character SHA of the merge commit, taken from the merged pull request |
92
+ | `version` | `X.Y.Z`, without the leading `v` |
93
+
94
+ The ref selector picks which copy of the workflow file runs, not what gets released — leave it on
95
+ `master`. Everything is built from `commit`, so the run is unaffected by whatever lands on `master`
96
+ in the meantime, and a patch release cut from a release branch is dispatched the same way as any
97
+ other: the workflow does not care which branch the commit is on.
98
+
99
+ The two inputs say the same thing twice, once as a commit and once as a name, and the run stops
100
+ before anything is built unless they agree with each other and with the repository:
101
+
102
+ - `commit` has to be a full SHA that some branch contains,
103
+ - `version` has to be the `Steep::VERSION` that commit declares,
104
+ - CHANGELOG.md has to start with a section for `version` (skipped for `.dev.N`, which is not
105
+ written up),
106
+ - `vX.Y.Z` must not exist yet.
107
+
108
+ It then:
109
+
110
+ - builds `steep-X.Y.Z.gem`,
111
+ - checks its metadata: the platform, no C extension, `exe/steep` and `lib/steep.rb` present, and
112
+ none of the development directories shipped,
113
+ - installs the gem the way a user would and type checks a small project with it — one that has to
114
+ pass and one that has to fail — so the executable, the dependencies, and the type checker are
115
+ exercised before anything is published,
116
+ - uploads the gem as an artifact,
117
+ - tags `commit` as `vX.Y.Z` and pushes the tag,
118
+ - pushes the gem to RubyGems through trusted publishing,
119
+ - publishes the GitHub release with the notes from CHANGELOG.md, skipping this last step for
120
+ `.dev.N` versions.
121
+
122
+ The tag is created once the gem is known to build and run, and before anything is published: a
123
+ tag can be deleted, while a version pushed to RubyGems can only be yanked.
124
+
125
+ Checking the `dry_run` box runs everything up to the artifact and stops — no tag, no gem pushed,
126
+ no release — which is how the build is exercised without releasing. `version` still has to match
127
+ the commit, so a dry run is also how a release is rehearsed before it is cut.
128
+
129
+ ## The version on `master`
130
+
131
+ `Steep::VERSION` on `master` is read one of two ways, told apart by how the version ends:
132
+
133
+ | On `master` | Means |
134
+ | --- | --- |
135
+ | `X.Y.0.dev` — a bare `.dev` | `X.Y.0` is being developed |
136
+ | A complete version — `X.Y.Z`, `X.Y.Z.pre.N`, `X.Y.Z.dev.N` | The version *after* the one named is being developed |
137
+
138
+ So `2.0.0` on `master` is not a claim that `master` is 2.0.0. It says 2.0.0 has shipped and what
139
+ comes after it is being worked on. Both become true the moment the release is tagged, so **nothing
140
+ has to be done to `master` after a release**.
141
+
142
+ The bare `X.Y.0.dev` is the exception because it is the one version that names a target rather than
143
+ a predecessor: a new minor is developed towards `X.Y.0` for a long time, before it is known whether
144
+ the next thing to ship is `X.Y.0.pre.1` or `X.Y.0` itself. Setting it is the only version change
145
+ that has to be made deliberately.
146
+
147
+ `rake gem:changelog` reads `Steep::VERSION` too, to decide where the next changelog starts — but
148
+ the version is set to the one being released before the changelog is generated, so it sees that
149
+ rather than whatever `master` was carrying.
150
+
151
+ ## Starting a new minor
152
+
153
+ `master` is the development line of one minor at a time. Moving it from `X.Y` to `X.(Y+1)` is not
154
+ part of any one release — it is the decision that the `X.Y` line is done, taken whenever that
155
+ becomes true — and it is the one moment the version on `master` is changed by hand. Two changes, in
156
+ opposite places:
157
+
158
+ 1. **Branch the line being left behind**, from the last `master` commit that belongs to it:
159
+
160
+ ```console
161
+ $ git switch --create aaa-X.Y.x <that commit>
162
+ $ git push -u origin aaa-X.Y.x
163
+ ```
164
+
165
+ Branch from the commit *before* the bump below, so the branch keeps the version its line was
166
+ released under. Patch releases of `X.Y` are cut from here from now on, with their changes
167
+ cherry-picked from `master` — see [Backports](#backports). The `aaa-` prefix carries no meaning
168
+ beyond sorting the release branches to the top of the branch list.
169
+
170
+ 2. **Bump `master`** to `X.(Y+1).0.dev`, in a pull request labeled `skip-changelog` like the
171
+ release pull request itself.
172
+
173
+ One loose end that is easy to forget: **the release note of the new line**. `rake gem:gh_release`
174
+ links every published release to `https://github.com/soutaro/steep/wiki/Release-Note-X.Y`, built
175
+ from the version number without checking that the page is there. Nothing has to be written when
176
+ the line starts — the page comes together as the first release proper of the line comes into view
177
+ — but it does have to exist by the time that release is published, or its notes link to an empty
178
+ page.
179
+
180
+ ## Backports
181
+
182
+ A patch release is cut from a release branch (`aaa-X.Y.x`), and what it carries beyond the previous
183
+ release is cherry-picked from the development line. Cherry-pick with `-x`:
184
+
185
+ ```console
186
+ $ git cherry-pick -x <commit>
187
+ ```
188
+
189
+ `-x` records the commit the change was copied from, and that recorded line is what `rake
190
+ gem:changelog` follows to reach the pull request the change was written and reviewed in. Without
191
+ it, the only pull request a backported commit is associated with is the one that carried the
192
+ backport, which says nothing about the change and is the same for every commit it brought over.
193
+
194
+ ## Assembling the changelog without `gh`
195
+
196
+ A release can be prepared from a Claude Code on the web session, with one exception:
197
+ `gem:changelog` and `gem:changelog:json` cannot run there. Both go through `gh`, and in such a
198
+ session `api.github.com` is blocked at the agent proxy for anything the shell does. `gh` is not
199
+ installed, installing it does not help, and rewriting the tasks against REST or Net::HTTP would be
200
+ blocked the same way — the refusal is keyed on the session rather than on the client.
201
+
202
+ Nothing else in the release is affected. `gem:check_release` and `gem:tag` read git and the working
203
+ tree, and `gem:gh_release` runs on a runner, where `gh` and `github.token` both work.
204
+
205
+ What the session does have is the GitHub MCP server, which reaches the API through its own
206
+ credentials. The changelog is assembled with its tools, in the three steps the rake task takes.
207
+
208
+ **1. Where the changelog starts.** The rule is `changelog_base`: a prerelease starts from the
209
+ latest tag, a release proper skips the prerelease tags. Tags are not fetched by default.
210
+
211
+ ```console
212
+ $ git fetch origin --tags
213
+ $ git describe --tags --match 'v*' --abbrev=0 --exclude 'v*.pre*' --exclude 'v*.dev*'
214
+ v2.0.0
215
+ ```
216
+
217
+ Drop the two `--exclude` flags for a prerelease, which starts at the latest tag whatever it is.
218
+
219
+ **2. The commits.**
220
+
221
+ ```console
222
+ $ git log --format=%H v2.0.0..HEAD
223
+ ```
224
+
225
+ **3. The pull requests they came from.** List the merged pull requests with `list_pull_requests`
226
+ (`base: master`, `state: closed`, `sort: updated`, `direction: desc`, and `fields: number, title,
227
+ labels, merged_at, head`), paging back until `merged_at` predates the base tag, and keep the ones
228
+ whose `head.sha` appears in the commit list from step 2. That intersection is what the task's
229
+ GraphQL `associatedPullRequests` query answers, reached from the other side.
230
+
231
+ Then drop the pull requests labeled `skip-changelog` and format the rest newest first, which is the
232
+ order of step 2:
233
+
234
+ ```markdown
235
+ * {title} ([#{number}](https://github.com/soutaro/steep/pull/{number}))
236
+ ```
237
+
238
+ Sorting them into sections needs the changed files, which `gem:changelog:json` would have supplied:
239
+ `pull_request_read` with `get_files` per pull request, or `get` for the body.
240
+
241
+ Four things about that matching, the first of which is a trap:
242
+
243
+ - **Do not read the numbers from `Merge pull request #N` commit subjects.** A squashed or rebased
244
+ pull request never writes that subject at all. Matching `head.sha` does not have that failure
245
+ mode.
246
+ - `head.sha` is in the history because this repository merges pull requests with merge commits. A
247
+ squashed or rebased one would need its `merge_commit_sha`, which the listing does not carry.
248
+ - The listing reports `merged: false` for pull requests that are merged — the field is not
249
+ populated by that endpoint. Read `merged_at` instead.
250
+ - On a release branch the commits are cherry-picks, so resolve the `(cherry picked from commit
251
+ <sha>)` trailer first and match the recorded origin, as `changelog_origins` does. Matching the
252
+ cherry-pick itself attributes every backport to the pull request that carried it.
253
+
254
+ ## Notes
255
+
256
+ - Prereleases (`X.Y.Z.pre.N`) are only installed with `gem install steep --pre`; a plain
257
+ `gem install steep` is unaffected.
258
+ - `rake 'gem:check_release[X.Y.Z]'` and `rake gem:tag` are what the workflow runs to check the
259
+ release and to create the tag. Both work locally, which is the fallback if the tag ever has to
260
+ be created by hand.
261
+ - Those two tasks and `rake gem:gh_release` come from the Rakefile of the commit being released,
262
+ not from the branch the workflow was dispatched from. Releasing from a release branch
263
+ (`aaa-X.Y.x`) therefore needs the release tooling on that branch as well; without it the run
264
+ fails on the missing task, before publishing anything.
@@ -112,7 +112,7 @@ module Steep
112
112
  send = case node.type
113
113
  when :send, :csend
114
114
  node
115
- when :block, :numblock
115
+ when :block, :numblock, :itblock
116
116
  node.children[0]
117
117
  end
118
118
 
@@ -163,7 +163,7 @@ module Steep
163
163
  send = case node.type
164
164
  when :send, :csend
165
165
  node
166
- when :block, :numblock
166
+ when :block, :numblock, :itblock
167
167
  node.children[0]
168
168
  end
169
169
 
@@ -245,7 +245,7 @@ module Steep
245
245
  loc ||= node.loc.operator if node.loc.respond_to?(:operator) # steep:ignore NoMethod
246
246
  loc ||= node.loc.selector if node.loc.respond_to?(:selector) # steep:ignore NoMethod
247
247
  loc
248
- when :block
248
+ when :block, :numblock, :itblock
249
249
  node.children[0].loc.selector
250
250
  end
251
251
  super(node: node, location: loc || node.loc.expression)
@@ -1061,7 +1061,7 @@ module Steep
1061
1061
  def header_line
1062
1062
  header =
1063
1063
  case node&.type
1064
- when :send, :csend, :block, :numblock
1064
+ when :send, :csend, :block, :numblock, :itblock
1065
1065
  "The method is deprecated"
1066
1066
  when :const, :casgn
1067
1067
  "The constant is deprecated"
@@ -220,7 +220,7 @@ module Steep
220
220
 
221
221
  def private_send?(node)
222
222
  case node.type
223
- when :block, :numblock
223
+ when :block, :numblock, :itblock
224
224
  private_send?(node.children[0])
225
225
  when :send, :csend
226
226
  receiver, = deconstruct_send_node!(node)
@@ -243,7 +243,7 @@ module Steep
243
243
  when :send, :csend, :super
244
244
  if block_node
245
245
  case block_node.type
246
- when :block, :numblock
246
+ when :block, :numblock, :itblock
247
247
  if send_node.equal?(block_node.children[0])
248
248
  return [send_node, block_node]
249
249
  end
@@ -422,7 +422,7 @@ module Steep
422
422
  when :send
423
423
  location = (_ = node.location) #: Parser::AST::_SelectorLocation
424
424
  if test_ast_location(location.selector, line: line, column: column)
425
- if (parent = parents[0]) && parent.type == :block && parent.children[0] === node
425
+ if (parent = parents[0]) && (parent.type == :block || parent.type == :numblock || parent.type == :itblock) && parent.children[0] === node
426
426
  node = parents[0]
427
427
  end
428
428
 
@@ -103,7 +103,7 @@ module Steep
103
103
  when :send, :csend
104
104
  result_node =
105
105
  case parents[0]&.type
106
- when :block, :numblock
106
+ when :block, :numblock, :itblock
107
107
  if node == parents.fetch(0).children[0]
108
108
  parents.fetch(0)
109
109
  else
@@ -59,7 +59,7 @@ module Steep
59
59
  if begin_loc.end_pos <= pos && pos <= end_loc.begin_pos
60
60
  # Given position is between open/close parens of args of send node
61
61
 
62
- if parent && (parent.type == :block || parent.type == :numblock) && node.equal?(parent.children[0])
62
+ if parent && (parent.type == :block || parent.type == :numblock || parent.type == :itblock) && node.equal?(parent.children[0])
63
63
  send_node = parent
64
64
  else
65
65
  send_node = node
data/lib/steep/source.rb CHANGED
@@ -31,7 +31,7 @@ module Steep
31
31
  end
32
32
 
33
33
  def self.new_parser
34
- Prism::Translation::Parser33.new(Builder.new).tap do |parser|
34
+ Prism::Translation::Parser34.new(Builder.new).tap do |parser|
35
35
  parser.diagnostics.all_errors_are_fatal = true
36
36
  parser.diagnostics.ignore_warnings = true
37
37
  end
@@ -257,7 +257,7 @@ module Steep
257
257
  annot.line or next
258
258
 
259
259
  case node.type
260
- when :def, :module, :class, :block, :numblock, :ensure, :defs, :resbody
260
+ when :def, :module, :class, :block, :numblock, :itblock, :ensure, :defs, :resbody
261
261
  location = node.loc
262
262
  location.line <= annot.line && annot.line < location.last_line
263
263
  else
@@ -565,13 +565,13 @@ module Steep
565
565
  end
566
566
  ]
567
567
  )
568
- when :numblock
569
- send, size, body = node.children
568
+ when :numblock, :itblock
569
+ send, arg, body = node.children
570
570
  node = node.updated(
571
571
  nil,
572
572
  [
573
573
  map_child_node(send) {|child| insert_type_node(child, child_assertions) },
574
- size,
574
+ arg,
575
575
  insert_type_node(body, child_assertions)
576
576
  ]
577
577
  )
@@ -648,7 +648,7 @@ module Steep
648
648
  case node.type
649
649
  when :send, :csend
650
650
  node
651
- when :block, :numblock
651
+ when :block, :numblock, :itblock
652
652
  send = node.children[0]
653
653
  case send.type
654
654
  when :send, :csend
@@ -2696,7 +2696,7 @@ module Steep
2696
2696
  constr.add_typing(node, type: type)
2697
2697
  end
2698
2698
 
2699
- when :block, :numblock, :send, :csend
2699
+ when :block, :numblock, :itblock, :send, :csend
2700
2700
  synthesize_sendish(node, hint: hint, tapp: nil)
2701
2701
 
2702
2702
  when :forwarded_args, :forward_arg
@@ -2772,6 +2772,20 @@ module Steep
2772
2772
 
2773
2773
  params = Parser::AST::Node.new(:args, arg_nodes)
2774
2774
 
2775
+ if send_node.type == :lambda
2776
+ # @type var node: Parser::AST::Node & Parser::AST::_BlockNode
2777
+ type_lambda(node, params_node: params, body_node: body, type_hint: hint)
2778
+ else
2779
+ type_send(node, send_node: send_node, block_params: params, block_body: body, unwrap: send_node.type == :csend, tapp: tapp, hint: hint)
2780
+ end
2781
+ end
2782
+ when :itblock
2783
+ yield_self do
2784
+ send_node, _name, body = node.children
2785
+
2786
+ arg_nodes = [Parser::AST::Node.new(:procarg0, [:it])]
2787
+ params = Parser::AST::Node.new(:args, arg_nodes)
2788
+
2775
2789
  if send_node.type == :lambda
2776
2790
  # @type var node: Parser::AST::Node & Parser::AST::_BlockNode
2777
2791
  type_lambda(node, params_node: params, body_node: body, type_hint: hint)
@@ -3346,7 +3360,7 @@ module Steep
3346
3360
  end
3347
3361
  end
3348
3362
 
3349
- if node.type == :csend || ((node.type == :block || node.type == :numblock) && node.children[0].type == :csend)
3363
+ if node.type == :csend || ((node.type == :block || node.type == :numblock || node.type == :itblock) && node.children[0].type == :csend)
3350
3364
  optional_type = AST::Types::Union.build(types: [call.return_type, AST::Builtin.nil_type])
3351
3365
  call = call.with_return_type(optional_type)
3352
3366
  end
@@ -3509,7 +3523,7 @@ module Steep
3509
3523
 
3510
3524
  when AST::Types::Any
3511
3525
  case node.type
3512
- when :block, :numblock
3526
+ when :block, :numblock, :itblock
3513
3527
  # @type var node: Parser::AST::Node & Parser::AST::_BlockNode
3514
3528
  block_annotations = source.annotations(block: node, factory: checker.factory, context: nesting)
3515
3529
  block_params or raise
@@ -564,9 +564,12 @@ module Steep
564
564
  false_types << type
565
565
  end
566
566
  else
567
- # For === (for_receiver: false), non-literal types can match the literal in truthy branch
568
- # For == (for_receiver: true), non-literal types only go to falsy branch
569
- true_types << AST::Types::Literal.new(value: value_node.children[0]) unless for_receiver
567
+ # For `==`, narrow only when the literal can be a value of `type`.
568
+ # e.g. `Symbol == :fatal` can be true, but `SomeClass == :fatal` cannot.
569
+ literal_type = AST::Types::Literal.new(value: value_node.children[0])
570
+ if !for_receiver || subtyping?(sub_type: literal_type, super_type: type)
571
+ true_types << literal_type
572
+ end
570
573
  false_types << type
571
574
  end
572
575
  end
data/lib/steep/typing.rb CHANGED
@@ -106,7 +106,7 @@ module Steep
106
106
  set(body_begin_pos..body_end_pos, context)
107
107
  end
108
108
 
109
- when :block, :numblock
109
+ when :block, :numblock, :itblock
110
110
  range = block_range(node)
111
111
  set(range, context)
112
112
 
@@ -132,7 +132,7 @@ module Steep
132
132
  node.loc.begin.end_pos # steep:ignore NoMethod
133
133
  end
134
134
  end_pos = node.loc.end.begin_pos # steep:ignore NoMethod
135
- when :numblock
135
+ when :numblock, :itblock
136
136
  send_node, _ = node.children
137
137
  begin_pos = node.loc.begin.end_pos # steep:ignore NoMethod
138
138
  end_pos = node.loc.end.begin_pos # steep:ignore NoMethod
@@ -245,7 +245,7 @@ module Steep
245
245
  node.loc.begin.end_pos # steep:ignore NoMethod
246
246
  end
247
247
  end_pos = node.loc.end.begin_pos # steep:ignore NoMethod
248
- when :numblock
248
+ when :numblock, :itblock
249
249
  send_node, _ = node.children
250
250
  begin_pos = node.loc.begin.end_pos # steep:ignore NoMethod
251
251
  end_pos = node.loc.end.begin_pos # steep:ignore NoMethod
data/lib/steep/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Steep
2
- VERSION = "2.0.0"
2
+ VERSION = "2.1.0.dev.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: steep
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0.dev.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soutaro Matsumoto
@@ -267,6 +267,7 @@ files:
267
267
  - bin/steep-check.rb
268
268
  - bin/steep-prof
269
269
  - doc/narrowing.md
270
+ - doc/release.md
270
271
  - doc/shape.md
271
272
  - exe/steep
272
273
  - guides/README.md
@@ -448,7 +449,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
448
449
  - !ruby/object:Gem::Version
449
450
  version: '0'
450
451
  requirements: []
451
- rubygems_version: 4.0.6
452
+ rubygems_version: 4.0.18
452
453
  specification_version: 4
453
454
  summary: Gradual Typing for Ruby
454
455
  test_files: []