rbs 4.1.0 → 4.1.1.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 +4 -4
- data/.github/dependabot.yml +1 -1
- data/.github/workflows/bundle-update.yml +1 -1
- data/.github/workflows/release-gems.yml +164 -0
- data/Rakefile +324 -31
- data/config.yml +2 -0
- data/docs/release.md +114 -32
- data/include/rbs/ast.h +4 -4
- data/lib/rbs/version.rb +1 -1
- data/src/ast.c +2 -2
- data/src/parser.c +6 -3
- metadata +3 -3
- data/.github/workflows/milestone.yml +0 -91
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4c6080d3cf9cb3af2188509104f6b43f59f47a08313cc82ac9c1d9e052dc1dc9
|
|
4
|
+
data.tar.gz: 131ca181e7e1516b8b4b938e9fce97c97ab4c3ad2b73b47d3f0e4e42996797ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b6c4bb23f7b593523cad221f31bfdd4d3399e8b93aac56813e2731680a4965d5571ce85ddbded21cd5353993f53719e2f8de91e1d4be7932ec5116c0abb3643
|
|
7
|
+
data.tar.gz: 95f2e854dc6e2f9115f258809110ec920468c6e1aadedfc7b98a13decb4c501bc0fd59958e8ffb06077bf34faf32a6d7a0310f8b2fc56f5b7a6477b521fca949
|
data/.github/dependabot.yml
CHANGED
|
@@ -58,6 +58,6 @@ jobs:
|
|
|
58
58
|
--body "Automated weekly bundle update" \
|
|
59
59
|
--head "$(git rev-parse --abbrev-ref HEAD)" \
|
|
60
60
|
--base "${{ github.event.repository.default_branch }}" \
|
|
61
|
-
--label "
|
|
61
|
+
--label "skip-changelog"
|
|
62
62
|
|
|
63
63
|
gh pr merge --auto --merge "$(git rev-parse --abbrev-ref HEAD)"
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
name: Release gems
|
|
2
|
+
|
|
3
|
+
# Builds, publishes, and announces a release. Dispatch it against the `vX.Y.Z` tag
|
|
4
|
+
# of the release: the tag is created first so that everything published afterwards
|
|
5
|
+
# traces back to an immutable ref, and so the reversible step comes before the
|
|
6
|
+
# irreversible one.
|
|
7
|
+
#
|
|
8
|
+
# Dispatching against a branch builds and verifies the gems and stops there, which
|
|
9
|
+
# is how the build is exercised without releasing anything.
|
|
10
|
+
#
|
|
11
|
+
# | Gem | Platform | Parser |
|
|
12
|
+
# | -------------------- | ------------- | -------------------------------- |
|
|
13
|
+
# | `rbs-X.Y.Z.gem` | `ruby` (MRI) | C extension, compiled on install |
|
|
14
|
+
# | `rbs-X.Y.Z-java.gem` | `java` (JRuby)| `rbs_parser.wasm`, prebuilt here |
|
|
15
|
+
#
|
|
16
|
+
# See docs/release.md. The `java` gem is built on CRuby: the platform comes from
|
|
17
|
+
# `RBS_PLATFORM`, not from the engine running `gem build`. JRuby is only needed to
|
|
18
|
+
# run the result, which the workflow does before it would publish anything.
|
|
19
|
+
#
|
|
20
|
+
# One job on purpose. Tagging, pushing the gems, and opening the GitHub release
|
|
21
|
+
# all belong to a single release, and keeping them in one place keeps their order
|
|
22
|
+
# readable -- the tag is created before anything is published, so the reversible
|
|
23
|
+
# step comes before the irreversible one.
|
|
24
|
+
|
|
25
|
+
on:
|
|
26
|
+
workflow_dispatch:
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: read
|
|
30
|
+
|
|
31
|
+
env:
|
|
32
|
+
# Keep in sync with .github/workflows/wasm.yml and .github/workflows/jruby.yml.
|
|
33
|
+
WASI_SDK_VERSION: "33"
|
|
34
|
+
WASI_SDK_RELEASE: "33.0"
|
|
35
|
+
|
|
36
|
+
jobs:
|
|
37
|
+
release:
|
|
38
|
+
name: release
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
permissions:
|
|
41
|
+
contents: write # publish the GitHub release
|
|
42
|
+
id-token: write # trusted publishing to RubyGems
|
|
43
|
+
steps:
|
|
44
|
+
# The gemspec takes its file list from `git ls-files`, so both gems are built
|
|
45
|
+
# from the committed state.
|
|
46
|
+
- uses: actions/checkout@v7
|
|
47
|
+
- name: Set up Ruby
|
|
48
|
+
uses: ruby/setup-ruby@v1
|
|
49
|
+
with:
|
|
50
|
+
ruby-version: ruby
|
|
51
|
+
bundler: none
|
|
52
|
+
- name: Update rubygems & bundler
|
|
53
|
+
run: gem update --system
|
|
54
|
+
- name: Install gems
|
|
55
|
+
run: |
|
|
56
|
+
bundle config set --local without libs:profilers
|
|
57
|
+
bundle install --jobs 4 --retry 3
|
|
58
|
+
|
|
59
|
+
- name: Read the version
|
|
60
|
+
id: version
|
|
61
|
+
run: echo "version=$(ruby -e 'load "lib/rbs/version.rb"; print RBS::VERSION')" >> "$GITHUB_OUTPUT"
|
|
62
|
+
|
|
63
|
+
# Fail before spending a minute on the build, and before anything is pushed:
|
|
64
|
+
# the tag is what the release is named after, so it has to be the version the
|
|
65
|
+
# tagged commit actually declares.
|
|
66
|
+
- name: Check the tag against RBS::VERSION
|
|
67
|
+
if: github.ref_type == 'tag'
|
|
68
|
+
run: |
|
|
69
|
+
if [ "${{ github.ref_name }}" != "v${{ steps.version.outputs.version }}" ]; then
|
|
70
|
+
echo "::error::tag ${{ github.ref_name }} does not match RBS::VERSION ${{ steps.version.outputs.version }}"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
- name: Build the ruby gem
|
|
75
|
+
run: |
|
|
76
|
+
mkdir -p pkg
|
|
77
|
+
gem build rbs.gemspec -o "pkg/rbs-${{ steps.version.outputs.version }}.gem"
|
|
78
|
+
|
|
79
|
+
# `rake wasm:jruby_setup` compiles src/**/*.c to WebAssembly and copies the
|
|
80
|
+
# result to lib/rbs/wasm/, where the gemspec picks it up. clang runs as a
|
|
81
|
+
# subprocess, so this works on CRuby.
|
|
82
|
+
- name: Install the WASI SDK
|
|
83
|
+
run: |
|
|
84
|
+
url="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_RELEASE}-x86_64-linux.tar.gz"
|
|
85
|
+
mkdir -p "$HOME/wasi-sdk"
|
|
86
|
+
curl -sSL "$url" | tar xz --strip-components=1 -C "$HOME/wasi-sdk"
|
|
87
|
+
echo "WASI_SDK_PATH=$HOME/wasi-sdk" >> "$GITHUB_ENV"
|
|
88
|
+
- name: Build rbs_parser.wasm
|
|
89
|
+
run: bundle exec rake wasm:jruby_setup
|
|
90
|
+
|
|
91
|
+
- name: Build the java gem
|
|
92
|
+
env:
|
|
93
|
+
RBS_PLATFORM: java
|
|
94
|
+
run: gem build rbs.gemspec -o "pkg/rbs-${{ steps.version.outputs.version }}-java.gem"
|
|
95
|
+
|
|
96
|
+
# `git ls-files` vouches for everything else, but rbs_parser.wasm is a build
|
|
97
|
+
# artifact, so the java gem is the one that can come out quietly wrong.
|
|
98
|
+
- name: Check the built gems
|
|
99
|
+
run: |
|
|
100
|
+
ruby -rrubygems/package -e '
|
|
101
|
+
ruby_gem, java_gem = ARGV.map { Gem::Package.new(_1).spec }
|
|
102
|
+
|
|
103
|
+
raise "unexpected platform: #{ruby_gem.platform}" unless ruby_gem.platform.to_s == "ruby"
|
|
104
|
+
raise "the C extension is not declared" if ruby_gem.extensions.empty?
|
|
105
|
+
|
|
106
|
+
raise "unexpected platform: #{java_gem.platform}" unless java_gem.platform.to_s == "java"
|
|
107
|
+
raise "rbs_parser.wasm is missing" unless java_gem.files.include?("lib/rbs/wasm/rbs_parser.wasm")
|
|
108
|
+
raise "the java gem must not declare an extension" unless java_gem.extensions.empty?
|
|
109
|
+
|
|
110
|
+
[ruby_gem, java_gem].each { puts "#{_1.full_name}: #{_1.files.size} files" }
|
|
111
|
+
' "pkg/rbs-${{ steps.version.outputs.version }}.gem" \
|
|
112
|
+
"pkg/rbs-${{ steps.version.outputs.version }}-java.gem"
|
|
113
|
+
|
|
114
|
+
# The checks above cannot tell whether rbs_parser.wasm actually runs. Install
|
|
115
|
+
# the gem the way a user would -- jar-dependencies fetches Chicory and ASM
|
|
116
|
+
# from Maven during the install -- and parse something with it, so the
|
|
117
|
+
# WebAssembly runtime is exercised end to end before anything is published.
|
|
118
|
+
- name: Set up JRuby
|
|
119
|
+
uses: ruby/setup-ruby@v1
|
|
120
|
+
with:
|
|
121
|
+
ruby-version: jruby
|
|
122
|
+
bundler: none
|
|
123
|
+
- name: Check the java gem on JRuby
|
|
124
|
+
run: |
|
|
125
|
+
gem install "pkg/rbs-${{ steps.version.outputs.version }}-java.gem"
|
|
126
|
+
ruby -e '
|
|
127
|
+
require "rbs"
|
|
128
|
+
_, _, decls = RBS::Parser.parse_signature("class Foo end")
|
|
129
|
+
names = decls.map { _1.name.to_s }
|
|
130
|
+
raise "parsed #{names.inspect}, expected [\"Foo\"]" unless names == ["Foo"]
|
|
131
|
+
puts "#{RUBY_ENGINE} #{RUBY_VERSION}: rbs #{RBS::VERSION} parses through the WebAssembly runtime"
|
|
132
|
+
'
|
|
133
|
+
|
|
134
|
+
- name: Switch back to CRuby
|
|
135
|
+
uses: ruby/setup-ruby@v1
|
|
136
|
+
with:
|
|
137
|
+
ruby-version: ruby
|
|
138
|
+
bundler: none
|
|
139
|
+
|
|
140
|
+
# Uploaded before publishing, so a failed push still leaves the gems behind.
|
|
141
|
+
- uses: actions/upload-artifact@v7
|
|
142
|
+
with:
|
|
143
|
+
name: gems
|
|
144
|
+
path: pkg/*.gem
|
|
145
|
+
if-no-files-found: error
|
|
146
|
+
|
|
147
|
+
# Everything below runs only for a release tag.
|
|
148
|
+
- name: Configure RubyGems credentials
|
|
149
|
+
if: github.ref_type == 'tag'
|
|
150
|
+
# No floating major tag on this action, so the exact release is pinned.
|
|
151
|
+
uses: rubygems/configure-rubygems-credentials@v2.1.0
|
|
152
|
+
|
|
153
|
+
- name: Push the gems
|
|
154
|
+
if: github.ref_type == 'tag'
|
|
155
|
+
run: |
|
|
156
|
+
gem push "pkg/rbs-${{ steps.version.outputs.version }}.gem"
|
|
157
|
+
gem push "pkg/rbs-${{ steps.version.outputs.version }}-java.gem"
|
|
158
|
+
|
|
159
|
+
# Last, so that a failed push never announces a release that has no gems.
|
|
160
|
+
- name: Publish the GitHub release
|
|
161
|
+
if: github.ref_type == 'tag'
|
|
162
|
+
env:
|
|
163
|
+
GH_TOKEN: ${{ github.token }}
|
|
164
|
+
run: bundle exec rake gem:gh_release
|
data/Rakefile
CHANGED
|
@@ -55,8 +55,15 @@ task :confirm_lexer => :lexer do
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
task :confirm_templates => :templates do
|
|
58
|
-
puts "Testing if generated code
|
|
59
|
-
|
|
58
|
+
puts "Testing if generated code is updated with respect to templates"
|
|
59
|
+
|
|
60
|
+
# Every template generates the file it is named after: `templates/<path>.erb` generates `<path>`.
|
|
61
|
+
generated = Dir.glob("templates/**/*.erb").sort.map { _1.delete_prefix("templates/").delete_suffix(".erb") }
|
|
62
|
+
|
|
63
|
+
missing = generated.reject { File.exist?(_1) }
|
|
64
|
+
raise "Templates without a generated file: #{missing.join(", ")}. Is the `templates` task missing an entry?" unless missing.empty?
|
|
65
|
+
|
|
66
|
+
sh "git diff --exit-code -- #{generated.join(" ")}"
|
|
60
67
|
end
|
|
61
68
|
|
|
62
69
|
# Task to format C code using clang-format
|
|
@@ -147,6 +154,9 @@ end
|
|
|
147
154
|
rule %r{^include/(.*)\.c} => 'templates/%X.c.erb' do |t|
|
|
148
155
|
puts "⚠️⚠️⚠️ #{t.name} is older than #{t.source}. You may need to run `rake templates` ⚠️⚠️⚠️"
|
|
149
156
|
end
|
|
157
|
+
rule %r{^ext/(.*)\.c} => 'templates/%X.c.erb' do |t|
|
|
158
|
+
puts "⚠️⚠️⚠️ #{t.name} is older than #{t.source}. You may need to run `rake templates` ⚠️⚠️⚠️"
|
|
159
|
+
end
|
|
150
160
|
|
|
151
161
|
task :annotate do
|
|
152
162
|
sh "bin/generate_docs.sh"
|
|
@@ -495,46 +505,329 @@ NOTES
|
|
|
495
505
|
end
|
|
496
506
|
|
|
497
507
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
major, minor, patch, _pre = RBS::VERSION.split(".", 4)
|
|
501
|
-
major = major.to_i
|
|
502
|
-
minor = minor.to_i
|
|
503
|
-
patch = patch.to_i
|
|
508
|
+
# Pull requests with one of these labels are omitted from the changelog.
|
|
509
|
+
CHANGELOG_SKIP_LABELS = ["skip-changelog"]
|
|
504
510
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
511
|
+
# Resolves the commit-ish the changelog starts from.
|
|
512
|
+
#
|
|
513
|
+
# `version` is a version number, a tag, or any commit-ish. When it is omitted, the latest tag
|
|
514
|
+
# matching `tag_glob` is used, skipping the ones matching `exclude_globs`.
|
|
515
|
+
#
|
|
516
|
+
def resolve_changelog_base(version, tag_glob:, exclude_globs: [])
|
|
517
|
+
require "open3"
|
|
518
|
+
|
|
519
|
+
from =
|
|
520
|
+
if version
|
|
521
|
+
# `4.1.0` and `v4.1.0` both mean the tag `v4.1.0`, while `master` or a SHA is used as is.
|
|
522
|
+
version.match?(/\A\d/) ? "v#{version}" : version
|
|
523
|
+
else
|
|
524
|
+
command = ["git", "describe", "--tags", "--match", tag_glob, "--abbrev=0"]
|
|
525
|
+
exclude_globs.each { |glob| command.push("--exclude", glob) }
|
|
510
526
|
|
|
511
|
-
|
|
527
|
+
output, status = Open3.capture2(*command)
|
|
528
|
+
raise "🚨 Cannot detect the latest tag matching `#{tag_glob}`. Give the previous version explicitly." unless status.success?
|
|
529
|
+
output.chomp
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
_, status = Open3.capture2("git", "rev-parse", "--verify", "--quiet", "#{from}^{commit}")
|
|
533
|
+
raise "🚨 No such commit-ish: `#{from}`" unless status.success?
|
|
512
534
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
"pr",
|
|
516
|
-
"list",
|
|
517
|
-
"--limit=10000",
|
|
518
|
-
"--json",
|
|
519
|
-
"url,title,number",
|
|
520
|
-
"--search" ,
|
|
521
|
-
"milestone:\"#{milestone}\" is:merged sort:updated-desc -label:Released"
|
|
522
|
-
]
|
|
535
|
+
from
|
|
536
|
+
end
|
|
523
537
|
|
|
538
|
+
# Runs a GraphQL query against the repository of the working directory.
|
|
539
|
+
#
|
|
540
|
+
# `body` is the selection set inside `repository`, so a query can use the `$owner` and `$name`
|
|
541
|
+
# variables. Returns the contents of `data.repository`.
|
|
542
|
+
#
|
|
543
|
+
def changelog_graphql(body)
|
|
524
544
|
require "open3"
|
|
545
|
+
require "json"
|
|
546
|
+
|
|
547
|
+
@changelog_repository ||=
|
|
548
|
+
begin
|
|
549
|
+
output, status = Open3.capture2("gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner")
|
|
550
|
+
raise status.inspect unless status.success?
|
|
551
|
+
output.chomp.split("/", 2)
|
|
552
|
+
end
|
|
553
|
+
owner, name = @changelog_repository
|
|
554
|
+
|
|
555
|
+
query = <<~GRAPHQL
|
|
556
|
+
query($owner: String!, $name: String!) {
|
|
557
|
+
repository(owner: $owner, name: $name) {
|
|
558
|
+
#{body}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
GRAPHQL
|
|
562
|
+
|
|
563
|
+
output, status = Open3.capture2(
|
|
564
|
+
"gh", "api", "graphql",
|
|
565
|
+
"-f", "query=#{query}",
|
|
566
|
+
"-f", "owner=#{owner}",
|
|
567
|
+
"-f", "name=#{name}",
|
|
568
|
+
binmode: true
|
|
569
|
+
)
|
|
570
|
+
raise status.inspect unless status.success?
|
|
571
|
+
|
|
572
|
+
# GitHub always answers in UTF-8, while the default external encoding follows the locale. Without
|
|
573
|
+
# this, a pull request body with an emoji fails to parse under `LANG=C`, as in GitHub Actions.
|
|
574
|
+
JSON.parse(output.force_encoding(Encoding::UTF_8), symbolize_names: true).dig(:data, :repository)
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# Lists the commits between `from` and `HEAD`, newest first.
|
|
578
|
+
#
|
|
579
|
+
# Giving `paths` limits the commits to the ones touching the paths.
|
|
580
|
+
#
|
|
581
|
+
def changelog_commits(from, paths: [])
|
|
582
|
+
require "open3"
|
|
583
|
+
|
|
584
|
+
command = ["git", "log", "--format=%H", "#{from}..HEAD"]
|
|
585
|
+
# `--simplify-merges` keeps the default history simplification from following only one parent of
|
|
586
|
+
# a merge commit, which can drop the other side. Note that `--full-history` alone is wrong here:
|
|
587
|
+
# it also lists merge commits that do not touch the paths, bringing back the excluded pull
|
|
588
|
+
# requests. The two flags produce the same commits as the default mode for this repository today.
|
|
589
|
+
command.push("--full-history", "--simplify-merges", "--", *paths) unless paths.empty?
|
|
590
|
+
|
|
525
591
|
output, status = Open3.capture2(*command)
|
|
526
592
|
raise status.inspect unless status.success?
|
|
527
593
|
|
|
528
|
-
|
|
529
|
-
|
|
594
|
+
output.lines.map(&:chomp).reject(&:empty?)
|
|
595
|
+
end
|
|
530
596
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
597
|
+
# Finds the pull requests the commits came from, keeping the order of `commits`.
|
|
598
|
+
#
|
|
599
|
+
# Returns the pull requests for the changelog and the ones omitted by `skip_labels`.
|
|
600
|
+
#
|
|
601
|
+
def changelog_pull_requests(commits, skip_labels: CHANGELOG_SKIP_LABELS)
|
|
602
|
+
pull_requests = {}
|
|
603
|
+
skipped = {}
|
|
604
|
+
|
|
605
|
+
commits.each_slice(50) do |slice|
|
|
606
|
+
# Ask GitHub which pull requests the commits came from, so that any merge strategy -- merge
|
|
607
|
+
# commit, squash, or rebase -- is handled without parsing commit messages.
|
|
608
|
+
aliases = slice.map.with_index do |commit, index|
|
|
609
|
+
<<~GRAPHQL
|
|
610
|
+
c#{index}: object(oid: "#{commit}") {
|
|
611
|
+
... on Commit {
|
|
612
|
+
associatedPullRequests(first: 10) {
|
|
613
|
+
nodes {
|
|
614
|
+
number title url merged
|
|
615
|
+
labels(first: 100) { nodes { name } }
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
GRAPHQL
|
|
535
621
|
end
|
|
622
|
+
|
|
623
|
+
changelog_graphql(aliases.join("\n")).each_value do |commit|
|
|
624
|
+
next unless commit
|
|
625
|
+
|
|
626
|
+
commit.dig(:associatedPullRequests, :nodes).each do |pr|
|
|
627
|
+
next unless pr[:merged]
|
|
628
|
+
|
|
629
|
+
pr = { number: pr[:number], title: pr[:title], url: pr[:url], labels: pr.dig(:labels, :nodes).map { |label| label[:name] } }
|
|
630
|
+
if (pr[:labels] & skip_labels).empty?
|
|
631
|
+
pull_requests[pr[:number]] ||= pr
|
|
632
|
+
else
|
|
633
|
+
skipped[pr[:number]] ||= pr
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
[pull_requests.values, skipped.values]
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# Fetches the details that help classifying the pull requests: the changed files and the body.
|
|
643
|
+
#
|
|
644
|
+
def changelog_pull_request_details(pull_requests)
|
|
645
|
+
pull_requests.each_slice(50).flat_map do |slice|
|
|
646
|
+
aliases = slice.map do |pr|
|
|
647
|
+
<<~GRAPHQL
|
|
648
|
+
p#{pr[:number]}: pullRequest(number: #{pr[:number]}) {
|
|
649
|
+
body
|
|
650
|
+
author { login }
|
|
651
|
+
files(first: 100) {
|
|
652
|
+
nodes { path }
|
|
653
|
+
pageInfo { hasNextPage }
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
GRAPHQL
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
details = changelog_graphql(aliases.join("\n"))
|
|
660
|
+
|
|
661
|
+
slice.map do |pr|
|
|
662
|
+
detail = details[:"p#{pr[:number]}"] or next pr
|
|
663
|
+
|
|
664
|
+
pr.merge(
|
|
665
|
+
author: detail.dig(:author, :login),
|
|
666
|
+
# The body is a hint for writing the changelog, not a copy source. Keep it short.
|
|
667
|
+
body: detail[:body].to_s.strip.slice(0, 1000),
|
|
668
|
+
files: detail.dig(:files, :nodes).map { |file| file[:path] },
|
|
669
|
+
files_truncated: detail.dig(:files, :pageInfo, :hasNextPage)
|
|
670
|
+
)
|
|
671
|
+
end
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# Reports the pull requests omitted by their label, so that they do not disappear silently.
|
|
676
|
+
#
|
|
677
|
+
def warn_skipped_pull_requests(skipped, skip_labels)
|
|
678
|
+
return if skipped.empty?
|
|
679
|
+
|
|
680
|
+
numbers = skipped.map { |pr| "##{pr[:number]}" }
|
|
681
|
+
numbers = numbers.take(20).push("and #{numbers.size - 20} more") if numbers.size > 20
|
|
682
|
+
|
|
683
|
+
$stderr.puts
|
|
684
|
+
$stderr.puts " (⏭️ Skipped #{skipped.size} pull request(s) labeled #{skip_labels.map { |label| "`#{label}`" }.join(" or ")}: #{numbers.join(", ")})"
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
# Prints the changelog template listing the pull requests merged between `from` and `HEAD`.
|
|
688
|
+
#
|
|
689
|
+
# The changelog goes to STDOUT and everything else goes to STDERR, so that the output can be
|
|
690
|
+
# piped to another command: `rake gem:changelog | pbcopy`
|
|
691
|
+
#
|
|
692
|
+
def print_changelog(from, paths: [], skip_labels: CHANGELOG_SKIP_LABELS)
|
|
693
|
+
$stderr.puts "🔍 Finding pull requests merged between `#{from}` and `HEAD`..."
|
|
694
|
+
|
|
695
|
+
commits = changelog_commits(from, paths: paths)
|
|
696
|
+
if commits.empty?
|
|
697
|
+
$stderr.puts " (🤔 There is no commit after `#{from}`.)"
|
|
698
|
+
return
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
pull_requests, skipped = changelog_pull_requests(commits, skip_labels: skip_labels)
|
|
702
|
+
|
|
703
|
+
if pull_requests.empty?
|
|
704
|
+
$stderr.puts " (🤔 No pull request is associated to the commits after `#{from}`.)"
|
|
536
705
|
else
|
|
537
|
-
puts
|
|
706
|
+
$stderr.puts
|
|
707
|
+
pull_requests.each do |pr|
|
|
708
|
+
puts "* #{pr[:title]} ([##{pr[:number]}](#{pr[:url]}))"
|
|
709
|
+
end
|
|
710
|
+
$stdout.flush
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
warn_skipped_pull_requests(skipped, skip_labels)
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
# Prints the same pull requests as `print_changelog` as JSON, with the details that help
|
|
717
|
+
# classifying them into the sections of CHANGELOG.md.
|
|
718
|
+
#
|
|
719
|
+
# This is the input for the release automation, so it always prints a valid JSON document.
|
|
720
|
+
#
|
|
721
|
+
def print_changelog_json(from, paths: [], skip_labels: CHANGELOG_SKIP_LABELS)
|
|
722
|
+
require "json"
|
|
723
|
+
|
|
724
|
+
$stderr.puts "🔍 Finding pull requests merged between `#{from}` and `HEAD`..."
|
|
725
|
+
|
|
726
|
+
commits = changelog_commits(from, paths: paths)
|
|
727
|
+
pull_requests, skipped = changelog_pull_requests(commits, skip_labels: skip_labels)
|
|
728
|
+
pull_requests = changelog_pull_request_details(pull_requests)
|
|
729
|
+
|
|
730
|
+
$stderr.puts " (📋 #{pull_requests.size} pull request(s))"
|
|
731
|
+
|
|
732
|
+
puts JSON.pretty_generate(
|
|
733
|
+
{
|
|
734
|
+
from: from,
|
|
735
|
+
to: "HEAD",
|
|
736
|
+
pull_requests: pull_requests,
|
|
737
|
+
skipped: skipped
|
|
738
|
+
}
|
|
739
|
+
)
|
|
740
|
+
$stdout.flush
|
|
741
|
+
|
|
742
|
+
warn_skipped_pull_requests(skipped, skip_labels)
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
namespace :gem do
|
|
746
|
+
# The gem is developed in the whole repository except the Rust crate, which has its own release
|
|
747
|
+
# cycle. Note that this is an *exclusion*, not a list of the directories shipped in the gem:
|
|
748
|
+
# changes in `test/` or `.github/` are part of the gem's changelog too.
|
|
749
|
+
# A constant defined in a `namespace` block is a top-level constant, so it needs the prefix.
|
|
750
|
+
GEM_CHANGELOG_PATHS = [".", ":(exclude)rust"]
|
|
751
|
+
|
|
752
|
+
# The tags a release proper starts *after*, rather than at.
|
|
753
|
+
GEM_PRERELEASE_TAGS = ["v*.pre*", "v*.dev*"]
|
|
754
|
+
|
|
755
|
+
# Where the changelog of the release being prepared starts, derived from `RBS::VERSION`:
|
|
756
|
+
#
|
|
757
|
+
# * `X.Y.Z.pre.N` documents what changed since `X.Y.Z.pre.N-1`, so it starts from the latest tag.
|
|
758
|
+
# * `X.Y.Z` documents the whole cycle, the prereleases included, so it skips the prerelease tags
|
|
759
|
+
# in between and starts from the previous release proper.
|
|
760
|
+
#
|
|
761
|
+
# This is the step that is easy to get wrong by hand: on a release proper the latest tag is a
|
|
762
|
+
# prerelease, so the obvious default would produce only the tail of the cycle. Passing a version
|
|
763
|
+
# explicitly overrides all of it.
|
|
764
|
+
#
|
|
765
|
+
def changelog_base(version)
|
|
766
|
+
excluded = Gem::Version.new(RBS::VERSION).prerelease? ? [] : GEM_PRERELEASE_TAGS
|
|
767
|
+
resolve_changelog_base(version, tag_glob: "v*", exclude_globs: excluded)
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
desc "Generate changelog template from GH pull requests merged since the previous release"
|
|
771
|
+
task :changelog, [:version] do |_task, args|
|
|
772
|
+
print_changelog(changelog_base(args[:version]), paths: GEM_CHANGELOG_PATHS)
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
namespace :changelog do
|
|
776
|
+
desc "Print the pull requests of `gem:changelog` as JSON, with the changed files and body of each"
|
|
777
|
+
task :json, [:version] do |_task, args|
|
|
778
|
+
print_changelog_json(changelog_base(args[:version]), paths: GEM_CHANGELOG_PATHS)
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
desc "Publish the GitHub release for RBS::VERSION, unless it is a `.dev.` version"
|
|
783
|
+
task :gh_release do
|
|
784
|
+
require "open3"
|
|
785
|
+
|
|
786
|
+
version = Gem::Version.new(RBS::VERSION)
|
|
787
|
+
major, minor, *_ = RBS::VERSION.split(".")
|
|
788
|
+
tag = "v#{RBS::VERSION}"
|
|
789
|
+
|
|
790
|
+
# There are three kinds of release: `X.Y.Z`, `X.Y.Z.pre.N`, and `X.Y.Z.dev.N`.
|
|
791
|
+
# The `.dev.N` ones are cut from the development line for people who need a
|
|
792
|
+
# specific change early; they are not written up in the changelog, so there are
|
|
793
|
+
# no notes to publish and nothing worth announcing.
|
|
794
|
+
if version.segments.include?("dev")
|
|
795
|
+
puts "⏭️ #{RBS::VERSION} is a dev release, so there is no GitHub release to publish."
|
|
796
|
+
next
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
# The release is created against an existing tag, so that the artifacts and the
|
|
800
|
+
# notes describe a commit that is already immutable.
|
|
801
|
+
_, status = Open3.capture2("git", "rev-parse", "--verify", "--quiet", "#{tag}^{commit}")
|
|
802
|
+
raise "🚨 No such tag: `#{tag}`. Tag the release before creating the GitHub release." unless status.success?
|
|
803
|
+
|
|
804
|
+
# The topmost section of the changelog is this release, minus its own heading.
|
|
805
|
+
# The encoding is explicit because the default external encoding follows the
|
|
806
|
+
# locale, and the changelog is not ASCII.
|
|
807
|
+
content = File.read(File.join(__dir__, "CHANGELOG.md"), encoding: Encoding::UTF_8)
|
|
808
|
+
section = content.scan(/^## \d.*?(?=^## \d)/m)[0] or raise "🚨 Cannot find a release section in CHANGELOG.md"
|
|
809
|
+
heading, _, body = section.partition("\n")
|
|
810
|
+
heading.include?(RBS::VERSION) or raise "🚨 CHANGELOG.md starts with `#{heading.strip}`, which is not #{RBS::VERSION}"
|
|
811
|
+
|
|
812
|
+
notes = <<~NOTES
|
|
813
|
+
[Release note](https://github.com/ruby/rbs/wiki/Release-Note-#{major}.#{minor})
|
|
814
|
+
|
|
815
|
+
#{body.strip}
|
|
816
|
+
NOTES
|
|
817
|
+
|
|
818
|
+
# Published rather than drafted: the notes are the changelog section that was
|
|
819
|
+
# already reviewed in the release pull request, so there is nothing left to edit.
|
|
820
|
+
command = [
|
|
821
|
+
"gh", "release", "create", tag,
|
|
822
|
+
"--title=#{RBS::VERSION}",
|
|
823
|
+
"--notes=#{notes}"
|
|
824
|
+
]
|
|
825
|
+
command << "--prerelease" if version.prerelease?
|
|
826
|
+
|
|
827
|
+
output, status = Open3.capture2(*command)
|
|
828
|
+
raise "🚨 `gh release create` failed: #{status.inspect}" unless status.success?
|
|
829
|
+
|
|
830
|
+
puts "📝 Released #{tag}: #{output.chomp}"
|
|
538
831
|
end
|
|
539
832
|
end
|
|
540
833
|
|
data/config.yml
CHANGED
|
@@ -769,6 +769,7 @@ nodes:
|
|
|
769
769
|
c_type: rbs_location_range
|
|
770
770
|
- name: type_name
|
|
771
771
|
c_type: rbs_type_name
|
|
772
|
+
optional: true # NULL when the name is left out, to be inferred from the Ruby code
|
|
772
773
|
- name: type_name_location
|
|
773
774
|
c_type: rbs_location_range
|
|
774
775
|
optional: true
|
|
@@ -781,6 +782,7 @@ nodes:
|
|
|
781
782
|
c_type: rbs_location_range
|
|
782
783
|
- name: type_name
|
|
783
784
|
c_type: rbs_type_name
|
|
785
|
+
optional: true # NULL when the name is left out, to be inferred from the Ruby code
|
|
784
786
|
- name: type_name_location
|
|
785
787
|
c_type: rbs_location_range
|
|
786
788
|
optional: true
|
data/docs/release.md
CHANGED
|
@@ -1,69 +1,151 @@
|
|
|
1
1
|
# Releasing RBS
|
|
2
2
|
|
|
3
|
+
A release is a pull request, a tag, and one workflow run. Everything that leaves
|
|
4
|
+
the repository — both gems and the GitHub release — is produced by the `Release
|
|
5
|
+
gems` workflow, so nothing has to be built or pushed from a laptop.
|
|
6
|
+
|
|
3
7
|
Each release ships **two gems**:
|
|
4
8
|
|
|
5
|
-
| Gem | Platform | Parser |
|
|
6
|
-
| --- | --- | --- |
|
|
7
|
-
| `rbs-X.Y.Z.gem` | `ruby` (MRI) | C extension
|
|
8
|
-
| `rbs-X.Y.Z-java.gem` | `java` (JRuby) |
|
|
9
|
+
| Gem | Platform | Parser |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `rbs-X.Y.Z.gem` | `ruby` (MRI) | C extension, compiled on install |
|
|
12
|
+
| `rbs-X.Y.Z-java.gem` | `java` (JRuby) | `rbs_parser.wasm`, built by the workflow |
|
|
9
13
|
|
|
10
14
|
The `-java` gem contains no native code — just `rbs_parser.wasm`. The Chicory/ASM
|
|
11
15
|
jars it needs are not shipped in the gem; they are declared as `jar-dependencies`
|
|
12
16
|
requirements and fetched from Maven when the gem is installed. So the gem can be
|
|
13
17
|
built once in any environment and runs on every JRuby.
|
|
14
18
|
|
|
19
|
+
There are three kinds of release, and they differ in what gets written up:
|
|
20
|
+
|
|
21
|
+
| Version | CHANGELOG section | GitHub release |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `X.Y.Z` | The whole cycle since the previous release proper, prereleases included | Published |
|
|
24
|
+
| `X.Y.Z.pre.N` | What changed since `X.Y.Z.pre.N-1` | Published, marked as a prerelease |
|
|
25
|
+
| `X.Y.Z.dev.N` | None | None |
|
|
26
|
+
|
|
27
|
+
`.dev.N` releases are cut from the development line for people who need a change
|
|
28
|
+
early, so they are gems and tags and nothing else.
|
|
29
|
+
|
|
15
30
|
## Prerequisites
|
|
16
31
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
32
|
+
Push rights to the `rbs` gem on RubyGems are **not** needed: the workflow
|
|
33
|
+
authenticates through a trusted publisher registered for this repository and
|
|
34
|
+
`release-gems.yml`. What is needed is write access to the repository, since that
|
|
35
|
+
is what lets you dispatch the workflow.
|
|
21
36
|
|
|
22
37
|
## Steps
|
|
23
38
|
|
|
24
|
-
### 1.
|
|
39
|
+
### 1. Prepare the release
|
|
40
|
+
|
|
41
|
+
Open a pull request that carries everything the release needs:
|
|
42
|
+
|
|
43
|
+
- `lib/rbs/version.rb` — set `RBS::VERSION` to the version being released.
|
|
44
|
+
- `Gemfile.lock` — run `bundle install` after the bump; the lockfile records the version too.
|
|
45
|
+
- `CHANGELOG.md` — add a section for the new version, directly under the `# CHANGELOG` heading.
|
|
46
|
+
Sections are newest first.
|
|
25
47
|
|
|
26
|
-
|
|
48
|
+
Label the pull request `skip-changelog`. It carries no change of its own, and without the label it
|
|
49
|
+
shows up in the next release's list — that is why 4.1.0's changelog contains a `Version 4.1.0`
|
|
50
|
+
entry.
|
|
51
|
+
|
|
52
|
+
`rake gem:changelog` lists the pull requests merged since the last release, already formatted:
|
|
27
53
|
|
|
28
54
|
```console
|
|
29
|
-
$ bundle exec rake
|
|
55
|
+
$ bundle exec rake gem:changelog | pbcopy
|
|
30
56
|
```
|
|
31
57
|
|
|
32
|
-
|
|
58
|
+
Where it starts follows `RBS::VERSION`, so bump the version first: a prerelease starts from the
|
|
59
|
+
latest tag, and a release proper skips the prerelease tags and starts from the previous release
|
|
60
|
+
proper. Pass a version to override it (`rake 'gem:changelog[4.1.0]'`). Only the list goes to
|
|
61
|
+
STDOUT, so it pipes cleanly. Pull requests labeled `skip-changelog` are left out and reported on
|
|
62
|
+
STDERR, and pull requests that only touch `rust/` are left out because the crates have their own
|
|
63
|
+
release cycle.
|
|
33
64
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- runs `release:note`, which opens a GitHub **draft** release (with
|
|
38
|
-
`--prerelease` for `*.pre.*` versions) and prints the remaining manual steps.
|
|
65
|
+
On a release proper, the `X.Y.Z.pre.N` sections above the previous release are replaced by the one
|
|
66
|
+
section being written — their pull requests are in it, and the notes they were published with stay
|
|
67
|
+
on their own GitHub releases.
|
|
39
68
|
|
|
40
|
-
|
|
69
|
+
Sort the list into the sections below. `rake gem:changelog:json` prints the same pull requests with
|
|
70
|
+
the changed files, labels, and body of each, which is what the sorting is based on.
|
|
41
71
|
|
|
42
|
-
|
|
72
|
+
```markdown
|
|
73
|
+
## X.Y.Z (YYYY-MM-DD)
|
|
43
74
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
75
|
+
### Signature updates
|
|
76
|
+
|
|
77
|
+
### Language updates
|
|
47
78
|
|
|
48
|
-
|
|
49
|
-
# jars are not bundled; they are fetched from Maven when the gem is installed.
|
|
50
|
-
$ docker run --rm -e RBS_PLATFORM=java -v "$PWD/pkg:/out" rbs-jruby \
|
|
51
|
-
gem build rbs.gemspec -o /out/rbs-X.Y.Z-java.gem
|
|
79
|
+
### Library changes
|
|
52
80
|
|
|
53
|
-
|
|
81
|
+
#### rbs prototype
|
|
82
|
+
|
|
83
|
+
#### rbs collection
|
|
84
|
+
|
|
85
|
+
### Miscellaneous
|
|
54
86
|
```
|
|
55
87
|
|
|
56
|
-
|
|
88
|
+
The sections always appear in this order; delete the ones that end up empty, which is most of them
|
|
89
|
+
on a small release. Two things scale with the size of the release:
|
|
90
|
+
|
|
91
|
+
- **Summary paragraphs**, above the first section. A patch release usually has none, 4.1.0 has four
|
|
92
|
+
paragraphs, and 4.0.0 has nine.
|
|
93
|
+
- **A list of the types whose signatures changed**, as the first line of `### Signature updates`,
|
|
94
|
+
written as `**Updated classes/modules/methods:**` followed by the names in backticks. Used on
|
|
95
|
+
`X.Y.0` releases only.
|
|
96
|
+
|
|
97
|
+
The date is the day the gem is released, matching the `vX.Y.Z` tag — not the day this pull request
|
|
98
|
+
is opened. Fix it up before step 2 if the pull request sat for a few days.
|
|
99
|
+
|
|
100
|
+
### 2. Tag the release
|
|
101
|
+
|
|
102
|
+
Once the pull request is merged, tag the merge commit and push the tag:
|
|
57
103
|
|
|
58
104
|
```console
|
|
59
|
-
$
|
|
60
|
-
|
|
105
|
+
$ git switch master && git pull
|
|
106
|
+
$ git tag "v$(ruby -e 'load "lib/rbs/version.rb"; print RBS::VERSION')"
|
|
107
|
+
$ git push origin --tags
|
|
61
108
|
```
|
|
62
109
|
|
|
110
|
+
The tag comes before anything is published, so that the gems and the release notes describe a
|
|
111
|
+
commit that is already immutable — and because a tag can be deleted, while a version pushed to
|
|
112
|
+
RubyGems can only be yanked.
|
|
113
|
+
|
|
114
|
+
### 3. Run the `Release gems` workflow against the tag
|
|
115
|
+
|
|
116
|
+
Dispatch [`release-gems.yml`](../.github/workflows/release-gems.yml) from the Actions tab, picking
|
|
117
|
+
the `vX.Y.Z` tag — **not** a branch — in the ref selector. The trusted publisher has no branch
|
|
118
|
+
condition, so the ref you pick is what decides what gets published; the workflow refuses to run
|
|
119
|
+
unless the tag matches `RBS::VERSION`.
|
|
120
|
+
|
|
121
|
+
It then:
|
|
122
|
+
|
|
123
|
+
- builds `rbs-X.Y.Z.gem`,
|
|
124
|
+
- compiles `rbs_parser.wasm` and builds `rbs-X.Y.Z-java.gem`,
|
|
125
|
+
- checks both: platforms, the C extension on one and its absence on the other, and that the wasm
|
|
126
|
+
module made it into the `java` gem,
|
|
127
|
+
- installs the `java` gem on JRuby and parses with it, so the WebAssembly runtime is exercised
|
|
128
|
+
before anything is published,
|
|
129
|
+
- uploads both gems as an artifact,
|
|
130
|
+
- pushes both to RubyGems through trusted publishing,
|
|
131
|
+
- publishes the GitHub release with the notes from CHANGELOG.md, skipping this last step for
|
|
132
|
+
`.dev.N` versions.
|
|
133
|
+
|
|
134
|
+
Dispatching against a branch runs everything up to the artifact and stops, which is how the build
|
|
135
|
+
is exercised without releasing.
|
|
136
|
+
|
|
137
|
+
### 4. Start the next development cycle
|
|
138
|
+
|
|
139
|
+
Open another pull request setting `RBS::VERSION` to the next prerelease (`4.1.1` → `4.1.2.pre`),
|
|
140
|
+
with `Gemfile.lock` regenerated, labeled `skip-changelog` like the release pull request itself.
|
|
141
|
+
Without it the version on `master` keeps claiming to be the released version for the whole
|
|
142
|
+
development period, and `rake gem:changelog` reads that version to decide where the next changelog
|
|
143
|
+
starts.
|
|
144
|
+
|
|
63
145
|
## Notes
|
|
64
146
|
|
|
65
147
|
- Prereleases (`X.Y.Z.pre.N`) are only installed with `gem install rbs --pre`;
|
|
66
148
|
a plain `gem install rbs` is unaffected. On JRuby, `gem install rbs [--pre]`
|
|
67
149
|
resolves to the `-java` gem automatically.
|
|
68
|
-
-
|
|
69
|
-
`wasm` and `
|
|
150
|
+
- `Dockerfile.jruby` pins the WASI SDK / Chicory / ASM versions to match the
|
|
151
|
+
`wasm`, `jruby`, and `release-gems` workflows. Keep them in sync when bumping.
|
data/include/rbs/ast.h
CHANGED
|
@@ -583,7 +583,7 @@ typedef struct rbs_ast_ruby_annotations_class_alias_annotation {
|
|
|
583
583
|
|
|
584
584
|
rbs_location_range prefix_location;
|
|
585
585
|
rbs_location_range keyword_location;
|
|
586
|
-
struct rbs_type_name *
|
|
586
|
+
struct rbs_type_name *RBS_NULLABLE type_name;
|
|
587
587
|
rbs_location_range type_name_location;
|
|
588
588
|
} rbs_ast_ruby_annotations_class_alias_annotation_t;
|
|
589
589
|
|
|
@@ -631,7 +631,7 @@ typedef struct rbs_ast_ruby_annotations_module_alias_annotation {
|
|
|
631
631
|
|
|
632
632
|
rbs_location_range prefix_location;
|
|
633
633
|
rbs_location_range keyword_location;
|
|
634
|
-
struct rbs_type_name *
|
|
634
|
+
struct rbs_type_name *RBS_NULLABLE type_name;
|
|
635
635
|
rbs_location_range type_name_location;
|
|
636
636
|
} rbs_ast_ruby_annotations_module_alias_annotation_t;
|
|
637
637
|
|
|
@@ -998,12 +998,12 @@ rbs_ast_members_prepend_t *RBS_NONNULL rbs_ast_members_prepend_new(rbs_allocator
|
|
|
998
998
|
rbs_ast_members_private_t *RBS_NONNULL rbs_ast_members_private_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location);
|
|
999
999
|
rbs_ast_members_public_t *RBS_NONNULL rbs_ast_members_public_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location);
|
|
1000
1000
|
rbs_ast_ruby_annotations_block_param_type_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_block_param_type_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range ampersand_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_location_range question_location, rbs_location_range type_location, rbs_node_t *RBS_NONNULL type_, rbs_location_range comment_location);
|
|
1001
|
-
rbs_ast_ruby_annotations_class_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_class_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *
|
|
1001
|
+
rbs_ast_ruby_annotations_class_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_class_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NULLABLE type_name, rbs_location_range type_name_location);
|
|
1002
1002
|
rbs_ast_ruby_annotations_colon_method_type_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_colon_method_type_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *RBS_NONNULL annotations, rbs_node_t *RBS_NONNULL method_type);
|
|
1003
1003
|
rbs_ast_ruby_annotations_double_splat_param_type_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_double_splat_param_type_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range star2_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_node_t *RBS_NONNULL param_type, rbs_location_range comment_location);
|
|
1004
1004
|
rbs_ast_ruby_annotations_instance_variable_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_instance_variable_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_ast_symbol_t *RBS_NONNULL ivar_name, rbs_location_range ivar_name_location, rbs_location_range colon_location, rbs_node_t *RBS_NONNULL type, rbs_location_range comment_location);
|
|
1005
1005
|
rbs_ast_ruby_annotations_method_types_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_method_types_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *RBS_NONNULL overloads, rbs_location_range_list_t *RBS_NONNULL vertical_bar_locations, rbs_location_range dot3_location);
|
|
1006
|
-
rbs_ast_ruby_annotations_module_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *
|
|
1006
|
+
rbs_ast_ruby_annotations_module_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NULLABLE type_name, rbs_location_range type_name_location);
|
|
1007
1007
|
rbs_ast_ruby_annotations_module_self_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_self_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_location_range colon_location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL args, rbs_location_range open_bracket_location, rbs_location_range close_bracket_location, rbs_location_range_list_t *RBS_NONNULL args_comma_locations, rbs_location_range comment_location);
|
|
1008
1008
|
rbs_ast_ruby_annotations_node_type_assertion_t *RBS_NONNULL rbs_ast_ruby_annotations_node_type_assertion_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_t *RBS_NONNULL type);
|
|
1009
1009
|
rbs_ast_ruby_annotations_param_type_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_param_type_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_node_t *RBS_NONNULL param_type, rbs_location_range comment_location);
|
data/lib/rbs/version.rb
CHANGED
data/src/ast.c
CHANGED
|
@@ -913,7 +913,7 @@ rbs_ast_ruby_annotations_block_param_type_annotation_t *RBS_NONNULL rbs_ast_ruby
|
|
|
913
913
|
return instance;
|
|
914
914
|
}
|
|
915
915
|
#line 140 "templates/src/ast.c.erb"
|
|
916
|
-
rbs_ast_ruby_annotations_class_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_class_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *
|
|
916
|
+
rbs_ast_ruby_annotations_class_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_class_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NULLABLE type_name, rbs_location_range type_name_location) {
|
|
917
917
|
rbs_ast_ruby_annotations_class_alias_annotation_t *instance = rbs_allocator_alloc(allocator, rbs_ast_ruby_annotations_class_alias_annotation_t);
|
|
918
918
|
|
|
919
919
|
*instance = (rbs_ast_ruby_annotations_class_alias_annotation_t) {
|
|
@@ -1001,7 +1001,7 @@ rbs_ast_ruby_annotations_method_types_annotation_t *RBS_NONNULL rbs_ast_ruby_ann
|
|
|
1001
1001
|
return instance;
|
|
1002
1002
|
}
|
|
1003
1003
|
#line 140 "templates/src/ast.c.erb"
|
|
1004
|
-
rbs_ast_ruby_annotations_module_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *
|
|
1004
|
+
rbs_ast_ruby_annotations_module_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NULLABLE type_name, rbs_location_range type_name_location) {
|
|
1005
1005
|
rbs_ast_ruby_annotations_module_alias_annotation_t *instance = rbs_allocator_alloc(allocator, rbs_ast_ruby_annotations_module_alias_annotation_t);
|
|
1006
1006
|
|
|
1007
1007
|
*instance = (rbs_ast_ruby_annotations_module_alias_annotation_t) {
|
data/src/parser.c
CHANGED
|
@@ -405,18 +405,21 @@ NODISCARD
|
|
|
405
405
|
static bool parse_keyword_key(rbs_parser_t *parser, rbs_ast_symbol_t **key) {
|
|
406
406
|
rbs_parser_advance(parser);
|
|
407
407
|
|
|
408
|
-
|
|
408
|
+
rbs_range_t symbol_range = parser->current_token.range;
|
|
409
409
|
|
|
410
410
|
if (parser->next_token.type == pQUESTION) {
|
|
411
|
+
// The `?` is part of the key, so it is part of the location too.
|
|
412
|
+
symbol_range.end = parser->next_token.range.end;
|
|
413
|
+
|
|
411
414
|
*key = rbs_ast_symbol_new(
|
|
412
415
|
ALLOCATOR(),
|
|
413
|
-
symbol_range,
|
|
416
|
+
RBS_RANGE_LEX2AST(symbol_range),
|
|
414
417
|
&parser->constant_pool,
|
|
415
418
|
intern_token_start_end(parser, parser->current_token, parser->next_token)
|
|
416
419
|
);
|
|
417
420
|
rbs_parser_advance(parser);
|
|
418
421
|
} else {
|
|
419
|
-
*key = rbs_ast_symbol_new(ALLOCATOR(), symbol_range, &parser->constant_pool, INTERN_TOKEN(parser, parser->current_token));
|
|
422
|
+
*key = rbs_ast_symbol_new(ALLOCATOR(), RBS_RANGE_LEX2AST(symbol_range), &parser->constant_pool, INTERN_TOKEN(parser, parser->current_token));
|
|
420
423
|
}
|
|
421
424
|
|
|
422
425
|
return true;
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rbs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.1.
|
|
4
|
+
version: 4.1.1.dev.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Soutaro Matsumoto
|
|
@@ -70,7 +70,7 @@ files:
|
|
|
70
70
|
- ".github/workflows/comments.yml"
|
|
71
71
|
- ".github/workflows/dependabot.yml"
|
|
72
72
|
- ".github/workflows/jruby.yml"
|
|
73
|
-
- ".github/workflows/
|
|
73
|
+
- ".github/workflows/release-gems.yml"
|
|
74
74
|
- ".github/workflows/ruby.yml"
|
|
75
75
|
- ".github/workflows/rust.yml"
|
|
76
76
|
- ".github/workflows/truffleruby.yml"
|
|
@@ -649,7 +649,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
649
649
|
- !ruby/object:Gem::Version
|
|
650
650
|
version: '0'
|
|
651
651
|
requirements: []
|
|
652
|
-
rubygems_version: 4.0.
|
|
652
|
+
rubygems_version: 4.0.17
|
|
653
653
|
specification_version: 4
|
|
654
654
|
summary: Type signature for Ruby.
|
|
655
655
|
test_files: []
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
name: Check milestone
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
types: [opened, edited, labeled, unlabeled, milestoned, demilestoned, synchronize]
|
|
6
|
-
merge_group: {}
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: read
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
check:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
|
|
15
|
-
steps:
|
|
16
|
-
# The milestone is a property of the pull request, so there is nothing to
|
|
17
|
-
# check once it enters the merge queue. This job still has to run there
|
|
18
|
-
# because `check` is a required status check -- it just reports success
|
|
19
|
-
# without doing anything.
|
|
20
|
-
- uses: actions/checkout@v7
|
|
21
|
-
if: github.event_name == 'pull_request'
|
|
22
|
-
|
|
23
|
-
- name: Extract RBS::Version
|
|
24
|
-
id: version
|
|
25
|
-
if: github.event_name == 'pull_request'
|
|
26
|
-
run: |
|
|
27
|
-
# Extract version string from lib/rbs/version.rb
|
|
28
|
-
version=$(ruby -e 'load "lib/rbs/version.rb"; print RBS::VERSION')
|
|
29
|
-
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
30
|
-
|
|
31
|
-
# Parse major.minor.patch
|
|
32
|
-
IFS='.' read -r major minor patch _rest <<< "$version"
|
|
33
|
-
echo "major=$major" >> "$GITHUB_OUTPUT"
|
|
34
|
-
echo "minor=$minor" >> "$GITHUB_OUTPUT"
|
|
35
|
-
echo "patch=$patch" >> "$GITHUB_OUTPUT"
|
|
36
|
-
|
|
37
|
-
echo "RBS::VERSION = $version (major=$major, minor=$minor, patch=$patch)"
|
|
38
|
-
|
|
39
|
-
- name: Check milestone
|
|
40
|
-
if: github.event_name == 'pull_request'
|
|
41
|
-
uses: actions/github-script@v9
|
|
42
|
-
with:
|
|
43
|
-
script: |
|
|
44
|
-
const pr = context.payload.pull_request;
|
|
45
|
-
const milestone = pr.milestone;
|
|
46
|
-
const labels = pr.labels.map(l => l.name);
|
|
47
|
-
|
|
48
|
-
const version = '${{ steps.version.outputs.version }}';
|
|
49
|
-
const major = '${{ steps.version.outputs.major }}';
|
|
50
|
-
const minor = '${{ steps.version.outputs.minor }}';
|
|
51
|
-
const patch = parseInt('${{ steps.version.outputs.patch }}', 10);
|
|
52
|
-
|
|
53
|
-
if (!milestone) {
|
|
54
|
-
if (labels.includes('no-milestone')) {
|
|
55
|
-
core.info('No milestone set, but no-milestone label is present — OK');
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
core.setFailed(
|
|
59
|
-
'No milestone set. Add a milestone or add the "no-milestone" label.'
|
|
60
|
-
);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (labels.includes('no-milestone')) {
|
|
65
|
-
core.setFailed(
|
|
66
|
-
'Milestone is set but "no-milestone" label is present. Remove the label or the milestone.'
|
|
67
|
-
);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const milestoneName = milestone.title;
|
|
72
|
-
core.info(`Milestone: "${milestoneName}", RBS::VERSION: ${version}`);
|
|
73
|
-
|
|
74
|
-
// Expected milestone based on version:
|
|
75
|
-
// patch == 0 → "RBS major.minor" (e.g. "RBS 4.0")
|
|
76
|
-
// patch >= 1 → "RBS major.minor.x" (e.g. "RBS 4.0.x")
|
|
77
|
-
let expectedMilestone;
|
|
78
|
-
if (patch === 0) {
|
|
79
|
-
expectedMilestone = `RBS ${major}.${minor}`;
|
|
80
|
-
} else {
|
|
81
|
-
expectedMilestone = `RBS ${major}.${minor}.x`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (milestoneName !== expectedMilestone) {
|
|
85
|
-
core.setFailed(
|
|
86
|
-
`Milestone "${milestoneName}" does not match RBS::VERSION ${version}. ` +
|
|
87
|
-
`Expected milestone: "${expectedMilestone}"`
|
|
88
|
-
);
|
|
89
|
-
} else {
|
|
90
|
-
core.info(`Milestone "${milestoneName}" matches RBS::VERSION ${version}`);
|
|
91
|
-
}
|