fix(parser): object-literal async shorthand methods now track async-function context (#129) #104
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Perf PR | |
| # Same-runner A/B microbenchmark check for pull requests. Benches the PR's | |
| # merge-base and its head on the SAME runner, back to back, then posts the | |
| # delta. Running both halves on one machine cancels the cross-runner CPU-tier | |
| # noise that makes comparing against a stored baseline unreliable — base-vs-head | |
| # in one job is the only honest signal on free shared runners. | |
| # | |
| # Informational only: it never fails the PR. The micro-benchmarks have real | |
| # within-run variance, so it reports deltas for a human to read rather than | |
| # gating. A budget gate could come later, once the noise floor is known. | |
| # | |
| # Opt-in: a two-pass bench costs runner minutes, so it runs only on PRs carrying | |
| # the `perf` label — add the label to run it, and it re-runs on each push while | |
| # the label is on; remove the label to stop. Without the label the job skips | |
| # instantly. The path filter is a second guard so a labeled doc-only PR skips. | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, labeled, unlabeled] | |
| paths: | |
| - 'pkg/**' | |
| - 'cmd/**' | |
| - '.github/workflows/perf-pr.yml' | |
| # Manual escape hatch: run against any PR by number without touching labels. | |
| workflow_dispatch: | |
| inputs: | |
| pr: | |
| description: 'PR number to benchmark' | |
| required: true | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: perf-pr-${{ github.event.pull_request.number || github.event.inputs.pr }} | |
| cancel-in-progress: true | |
| jobs: | |
| bench: | |
| if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'perf') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| outputs: | |
| pr: ${{ steps.refs.outputs.pr }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: go.mod | |
| - name: Resolve base and head | |
| id: refs | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| PR_FROM_EVENT: ${{ github.event.pull_request.number }} | |
| PR_FROM_INPUT: ${{ github.event.inputs.pr }} | |
| BASE_FROM_EVENT: ${{ github.event.pull_request.base.ref }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| # workflow_dispatch has no PR context, so take the number from the | |
| # input and look the base branch up via the API. pull_request carries | |
| # both directly. | |
| if [ "$EVENT" = "workflow_dispatch" ]; then | |
| pr="$PR_FROM_INPUT" | |
| [[ "$pr" =~ ^[0-9]+$ ]] || { echo "pr input must be a number, got: ${pr}" >&2; exit 1; } | |
| base_ref="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr}" --jq .base.ref)" | |
| else | |
| pr="$PR_FROM_EVENT" | |
| base_ref="$BASE_FROM_EVENT" | |
| fi | |
| # refs/pull/<n>/head is on the BASE repo for both same-repo and fork | |
| # PRs, so this resolves the head without needing fork remote access. | |
| git fetch --no-tags origin "$base_ref" "refs/pull/${pr}/head:pr-head" | |
| head_sha="$(git rev-parse pr-head)" | |
| # The merge-base is the PR's own fork point, so we measure the PR's | |
| # delta, not unrelated drift that landed on the base branch since. | |
| base_sha="$(git merge-base "origin/${base_ref}" pr-head)" | |
| { | |
| echo "pr=${pr}" | |
| echo "head=${head_sha}" | |
| echo "base=${base_sha}" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Base (merge-base): ${base_sha}" | |
| echo "Head: ${head_sha}" | |
| - name: Bench merge-base | |
| env: | |
| BASE: ${{ steps.refs.outputs.base }} | |
| run: | | |
| set -euo pipefail | |
| git checkout --force "$BASE" | |
| # Use the lower-envelope reducer for this throwaway per-run A/B baseline | |
| # (RUNNER_TEMP, not the durable ratchet). But the merge-base binary is built | |
| # from an OLD commit that may predate the -reducer flag, so feature-detect it | |
| # here and reuse the same decision for the head half (via GITHUB_ENV) — that | |
| # keeps both halves on the SAME reducer. A base too old for the flag falls | |
| # back to its default (mean), and head then omits the flag too (also mean), | |
| # so the informational compare stays like-for-like across the transition. | |
| # Capture then match (not a pipe): -h exits non-zero, which under | |
| # `set -o pipefail` would poison an `if ... | grep` and misreport support. | |
| RFLAG="" | |
| help="$(go run ./cmd/bench-ratchet -h 2>&1 || true)" | |
| case "$help" in *-reducer*) RFLAG="-reducer min" ;; esac | |
| echo "RFLAG=${RFLAG}" >> "$GITHUB_ENV" | |
| # shellcheck disable=SC2086 # RFLAG is intentionally split: "-reducer min" or empty | |
| go run ./cmd/bench-ratchet \ | |
| -baseline "${RUNNER_TEMP}/base.json" \ | |
| -count 3 -benchtime 500ms -timeout 15m \ | |
| ${RFLAG} \ | |
| update | |
| - name: Bench head and compare | |
| id: bench | |
| env: | |
| BASE: ${{ steps.refs.outputs.base }} | |
| HEAD: ${{ steps.refs.outputs.head }} | |
| run: | | |
| set -euo pipefail | |
| git checkout --force "$HEAD" | |
| # Inform-only: never fail the job on a regression (|| true). The | |
| # markdown report is the product; the exit code is ignored. | |
| # $RFLAG comes from the base half (via GITHUB_ENV) so both halves use the | |
| # same reducer — like-for-like even when the base was too old for the flag. | |
| # shellcheck disable=SC2086 # RFLAG is intentionally split: "-reducer min" or empty | |
| go run ./cmd/bench-ratchet \ | |
| -baseline "${RUNNER_TEMP}/base.json" \ | |
| -count 3 -benchtime 500ms -timeout 15m \ | |
| ${RFLAG:-} \ | |
| -budget 0.10 -allow-incomplete \ | |
| -format markdown \ | |
| check > "${RUNNER_TEMP}/report.md" || true | |
| run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" | |
| report() { | |
| echo "## Perf — base vs head, same runner" | |
| echo | |
| echo "Base \`${BASE:0:12}\` vs head \`${HEAD:0:12}\`. Anchor-normalized; informational, not a gate." | |
| echo | |
| sed -n '/\*\*bench-ratchet\*\*/,$p' "${RUNNER_TEMP}/report.md" | |
| } | |
| # Full table → job summary (the only channel that works on fork PRs, | |
| # where the comment token is read-only). | |
| report >> "$GITHUB_STEP_SUMMARY" | |
| { report; echo; echo "_[Run details →](${run_url})_"; } > "${RUNNER_TEMP}/comment.md" | |
| - name: Upload PR comment artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: perf-pr-comment | |
| path: ${{ runner.temp }}/comment.md | |
| if-no-files-found: error | |
| comment: | |
| needs: bench | |
| if: needs.bench.result == 'success' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: perf-pr-comment | |
| path: ${{ runner.temp }}/perf-pr-comment | |
| # Sticky PR comment (upsert by marker). Fork PRs get a read-only token, so | |
| # this can't post there — continue-on-error keeps the workflow green and | |
| # the benchmark job summary still carries the report. | |
| - name: Upsert PR comment | |
| continue-on-error: true | |
| uses: actions/github-script@v7 | |
| env: | |
| PR: ${{ needs.bench.outputs.pr }} | |
| COMMENT_PATH: ${{ runner.temp }}/perf-pr-comment/comment.md | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const marker = '<!-- perf-pr-report -->'; | |
| const body = marker + '\n' + fs.readFileSync(process.env.COMMENT_PATH, 'utf8'); | |
| const { owner, repo } = context.repo; | |
| const issue_number = Number(process.env.PR); | |
| const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number }); | |
| const existing = comments.find(c => c.body && c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| } |