Skip to content

Add Vitest lifecycle and structure APIs#184

Merged
frostney merged 6 commits into
mainfrom
feature/vitest-phase2-lifecycle-structure-118
Apr 4, 2026
Merged

Add Vitest lifecycle and structure APIs#184
frostney merged 6 commits into
mainfrom
feature/vitest-phase2-lifecycle-structure-118

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Apr 4, 2026

Summary

  • implement Vitest Phase 2 test APIs in Goccia.Builtins.TestAssertions
  • add suite-scoped lifecycle hooks, focused execution, parameterized test/suite expansion, and todo placeholders
  • cover the new TestAssertions behavior in the native Pascal test suite instead of JavaScript self-tests

Closes #118

Testing

  • ./build.pas clean tests
  • for t in build/Goccia.*.Test; do "$t"; done
  • ./build.pas testrunner && ./build/TestRunner tests --no-progress
  • ./format.pas --check

Summary by CodeRabbit

  • New Features
    • Added suite- and test-level modifiers (.only, .each, .skipIf, .runIf, .skip, .todo, it.only) and suite lifecycle hooks (beforeAll, afterAll)
  • Documentation
    • Expanded test framework docs and guide for focus (.only), placeholders (.todo), conditional execution, lifecycle hooks, and parameterized tests (.each)
  • Tests
    • Added tests validating lifecycle ordering, focus/skip/todo behavior, and parameterized expansion

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors test registration to a nested suite/test tree, adds suite-level hooks (beforeAll/afterAll), parameterized *.each registration, focus/skip/todo flags and corresponding *.only/test.todo APIs, and updates docs and unit tests to cover new behaviors. (50 words)

Changes

Cohort / File(s) Summary
Documentation Updates
docs/built-ins.md, docs/testing.md
Expanded built-ins and testing docs to list and demonstrate new APIs (describe.only, describe.each, test.only, test.each, test.todo, beforeAll, afterAll, etc.) plus examples for focus, placeholders, and parameterized tests.
Core Test Framework Implementation
units/Goccia.Builtins.TestAssertions.pas
Replaced flat registration model with a tree of TGocciaRegisteredEntry nodes (TGocciaTestSuite, TGocciaTestCase); added suite-scoped hooks (beforeAll/afterAll), inherited hook aggregation, per-entry flags (IsFocused, IsTodo, IsSkipped), parameterized registration support (*.each), focused-selection logic, GC root adjustments, and new registration APIs (DescribeOnly, DescribeEach, TestOnly, TestEach, TestTodo, BeforeAll, AfterAll).
Tests for New Features
units/Goccia.Builtins.TestAssertions.Test.pas
Added tests exercising beforeAll/afterAll ordering, .only behavior for test/it/describe, test.todo placeholders, and test.each/describe.each expansion; introduced event recording and helpers for running registrations and resolving callables.

Sequence Diagram(s)

sequenceDiagram
    participant User as "User Code"
    participant Reg as "Registration System"
    participant Tree as "Suite/Test Tree"
    participant Exec as "Execution Engine"
    participant Hooks as "Hooks Manager"
    participant Test as "Test Function"

    User->>Reg: call describe(name, fn) / test(...) / test.each(...)
    Reg->>Tree: create suite/test nodes and set flags (IsFocused/IsSkipped/IsTodo)
    Reg->>Tree: expand parameterized entries (each)

    Exec->>Tree: RunTests — traverse root suite
    Exec->>Hooks: run beforeAll for current suite
    Hooks-->>Exec: beforeAll done

    loop per test node (respecting focus/skips)
        Exec->>Hooks: collect & run inherited beforeEach
        Hooks-->>Exec: beforeEach done

        alt executable
            Exec->>Test: invoke test function with args
            Test-->>Exec: result (pass/fail)
        else skipped/todo
            Exec-->>Exec: record skipped/todo
        end

        Exec->>Hooks: run afterEach
        Hooks-->>Exec: afterEach done
    end

    Exec->>Hooks: run afterAll for current suite
    Hooks-->>Exec: afterAll done
    Exec-->>User: aggregate results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through suites and hooks in stride,

beforeAll warmed the burrow, then tests took a ride.
.only sniffed the carrots, .todo left one spare,
each row sprang to life — a parameterized hare.
Green checks and tidy logs — a grateful rabbit's cheer.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Vitest lifecycle and structure APIs' directly and accurately describes the main change: implementing Vitest Phase 2 APIs including lifecycle hooks and structure features.
Linked Issues check ✅ Passed All coding objectives from issue #118 are implemented: beforeAll/afterAll [#118], test.each/describe.each [#118], test.only/describe.only [#118], test.todo [#118], and corresponding test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing Vitest Phase 2 APIs: documentation updates, core implementation in TestAssertions, and comprehensive test coverage with no extraneous modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/vitest-phase2-lifecycle-structure-118

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
docs/testing.md (1)

343-358: Consider clarifying the example to avoid confusion.

The example reassigns instance in both beforeAll (line 344) and beforeEach (line 348), which may confuse readers about the intended hook usage pattern. The beforeEach assignment effectively shadows the beforeAll setup on every test run.

This is technically valid, but for documentation purposes, consider using distinct variables or separate examples to illustrate each hook's purpose more clearly.

📝 Suggested clarification
 describe("with setup", () => {
-  let instance;
+  let sharedFixture;
+  let instance;

   beforeAll(() => {
-    instance = createSharedFixture();
+    sharedFixture = createSharedFixture();
   });

   beforeEach(() => {
     instance = new MyClass();
   });

   afterEach(() => {
     // cleanup
   });

   afterAll(() => {
-    instance = null;
+    sharedFixture = null;
   });

   test("uses instance", () => {
     expect(instance).toBeTruthy();
+    expect(sharedFixture).toBeTruthy();
   });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/testing.md` around lines 343 - 358, The example uses the same variable
name instance in both beforeAll (calling createSharedFixture()) and beforeEach
(new MyClass()), which causes the beforeEach to shadow the beforeAll setup and
confuses readers; update the docs to show distinct usage by either (a) using
different variable names (e.g., sharedInstance for beforeAll and freshInstance
for beforeEach) or (b) splitting into two separate examples that demonstrate
beforeAll with createSharedFixture() and beforeEach with new MyClass()
independently, referencing the hooks beforeAll, beforeEach and the symbols
createSharedFixture, MyClass and instance to make the intent clear.
units/Goccia.Builtins.TestAssertions.pas (1)

474-482: Consider adding an else clause for defensive coding.

The case statement covers all current TGocciaParameterizedRegistrationTarget values, but lacks an else clause. If the enum is extended in the future, this could return an uninitialized string.

🛡️ Defensive else clause
 function TGocciaParameterizedRegistrationFunction.GetFunctionName: string;
 begin
   case FTarget of
     prtDescribe:
       Result := 'describe';
     prtTest:
       Result := 'test';
+  else
+    Result := 'unknown';
   end;
 end;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.TestAssertions.pas` around lines 474 - 482, The
GetFunctionName method lacks a fallback for unknown
TGocciaParameterizedRegistrationTarget values; update
TGocciaParameterizedRegistrationFunction.GetFunctionName to add an else branch
in the case over FTarget that defensively handles unexpected enum values (for
example set a safe default Result or raise an exception/assertion with a clear
message referencing the unexpected FTarget value) so the function never returns
an uninitialized string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/testing.md`:
- Around line 343-358: The example uses the same variable name instance in both
beforeAll (calling createSharedFixture()) and beforeEach (new MyClass()), which
causes the beforeEach to shadow the beforeAll setup and confuses readers; update
the docs to show distinct usage by either (a) using different variable names
(e.g., sharedInstance for beforeAll and freshInstance for beforeEach) or (b)
splitting into two separate examples that demonstrate beforeAll with
createSharedFixture() and beforeEach with new MyClass() independently,
referencing the hooks beforeAll, beforeEach and the symbols createSharedFixture,
MyClass and instance to make the intent clear.

In `@units/Goccia.Builtins.TestAssertions.pas`:
- Around line 474-482: The GetFunctionName method lacks a fallback for unknown
TGocciaParameterizedRegistrationTarget values; update
TGocciaParameterizedRegistrationFunction.GetFunctionName to add an else branch
in the case over FTarget that defensively handles unexpected enum values (for
example set a safe default Result or raise an exception/assertion with a clear
message referencing the unexpected FTarget value) so the function never returns
an uninitialized string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4b57ed0-8322-4207-8d59-712a29cca44d

📥 Commits

Reviewing files that changed from the base of the PR and between 837ebf2 and cf1c741.

📒 Files selected for processing (10)
  • docs/built-ins.md
  • docs/testing.md
  • tests/language/testing/afterAll.js
  • tests/language/testing/beforeAll.js
  • tests/language/testing/describe-each.js
  • tests/language/testing/describe-only.js
  • tests/language/testing/test-each.js
  • tests/language/testing/test-only.js
  • tests/language/testing/test-todo.js
  • units/Goccia.Builtins.TestAssertions.pas

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 4, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 3645 3645
Tests Passed 3604 ✅ 3645 ✅
Tests Skipped 41 0
Tests Execution 201.6ms 183.6ms
Tests Engine 373.1ms 650.0ms
Benchmarks Total 274 274
Benchmarks Duration 7.57min 6.46min

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 4, 2026

Benchmark Results

274 benchmarks

Interpreted: 🟢 93 improved · 🔴 32 regressed · 149 unchanged · avg +0.7%
Bytecode: 🟢 241 improved · 🔴 7 regressed · 26 unchanged · avg +9.2%

arraybuffer.js — Interp: 🟢 6, 🔴 1, 7 unch. · avg +0.9% · Bytecode: 🟢 13, 1 unch. · avg +13.2%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 449,041 ops/sec [444,567..449,815] → 452,606 ops/sec [339,984..458,746] ~ overlap (+0.8%) 487,979 ops/sec [463,018..491,277] → 561,566 ops/sec [558,500..564,597] 🟢 +15.1%
create ArrayBuffer(64) 428,672 ops/sec [423,786..430,909] → 438,439 ops/sec [436,061..438,713] 🟢 +2.3% 467,136 ops/sec [465,236..468,353] → 538,473 ops/sec [538,082..538,867] 🟢 +15.3%
create ArrayBuffer(1024) 337,675 ops/sec [335,105..338,559] → 341,261 ops/sec [331,913..342,758] ~ overlap (+1.1%) 363,729 ops/sec [362,150..366,336] → 427,405 ops/sec [426,444..427,880] 🟢 +17.5%
create ArrayBuffer(8192) 151,079 ops/sec [148,913..152,069] → 153,596 ops/sec [150,367..155,145] ~ overlap (+1.7%) 161,283 ops/sec [160,698..161,940] → 185,043 ops/sec [159,028..188,014] ~ overlap (+14.7%)
slice full buffer (64 bytes) 506,586 ops/sec [503,977..508,409] → 522,617 ops/sec [521,366..523,477] 🟢 +3.2% 589,108 ops/sec [587,394..590,918] → 660,019 ops/sec [657,731..663,326] 🟢 +12.0%
slice half buffer (512 of 1024 bytes) 439,670 ops/sec [436,910..442,263] → 441,417 ops/sec [440,350..444,249] ~ overlap (+0.4%) 508,056 ops/sec [504,943..511,817] → 560,888 ops/sec [559,362..562,860] 🟢 +10.4%
slice with negative indices 427,837 ops/sec [426,067..429,010] → 433,265 ops/sec [432,745..434,821] 🟢 +1.3% 545,433 ops/sec [543,878..546,781] → 600,075 ops/sec [593,379..608,730] 🟢 +10.0%
slice empty range 488,437 ops/sec [483,016..489,834] → 490,251 ops/sec [485,655..493,008] ~ overlap (+0.4%) 577,726 ops/sec [575,838..581,344] → 635,440 ops/sec [633,123..639,843] 🟢 +10.0%
byteLength access 1,297,305 ops/sec [1,288,780..1,299,319] → 1,327,405 ops/sec [1,316,970..1,331,917] 🟢 +2.3% 1,488,085 ops/sec [1,485,403..1,488,936] → 1,621,348 ops/sec [1,619,132..1,623,294] 🟢 +9.0%
Symbol.toStringTag access 1,010,725 ops/sec [1,008,262..1,012,875] → 1,019,743 ops/sec [1,016,344..1,027,725] 🟢 +0.9% 1,017,040 ops/sec [1,006,260..1,020,266] → 1,131,304 ops/sec [1,130,109..1,131,970] 🟢 +11.2%
ArrayBuffer.isView 721,122 ops/sec [718,708..722,107] → 734,004 ops/sec [729,436..737,783] 🟢 +1.8% 829,297 ops/sec [827,748..832,506] → 913,567 ops/sec [911,365..914,328] 🟢 +10.2%
clone ArrayBuffer(64) 414,903 ops/sec [414,176..417,017] → 409,365 ops/sec [402,754..410,867] 🔴 -1.3% 451,816 ops/sec [450,730..453,121] → 521,220 ops/sec [519,770..522,459] 🟢 +15.4%
clone ArrayBuffer(1024) 323,952 ops/sec [322,909..326,276] → 322,419 ops/sec [315,336..325,730] ~ overlap (-0.5%) 350,251 ops/sec [349,515..353,838] → 416,914 ops/sec [415,997..417,683] 🟢 +19.0%
clone ArrayBuffer inside object 265,667 ops/sec [264,451..266,540] → 261,834 ops/sec [259,238..264,801] ~ overlap (-1.4%) 285,086 ops/sec [283,912..286,879] → 327,744 ops/sec [326,983..328,111] 🟢 +15.0%
arrays.js — Interp: 🟢 2, 🔴 1, 16 unch. · avg +0.2% · Bytecode: 🟢 19 · avg +10.7%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 13,012 ops/sec [12,878..13,079] → 13,109 ops/sec [13,089..13,179] 🟢 +0.7% 13,556 ops/sec [13,465..13,603] → 14,661 ops/sec [14,493..14,760] 🟢 +8.1%
Array.from 10 elements 218,033 ops/sec [217,161..218,748] → 217,783 ops/sec [217,558..218,066] ~ overlap (-0.1%) 236,899 ops/sec [235,370..237,391] → 266,516 ops/sec [264,857..267,864] 🟢 +12.5%
Array.of 10 elements 277,304 ops/sec [275,848..277,739] → 269,881 ops/sec [268,829..270,136] 🔴 -2.7% 298,384 ops/sec [298,237..298,707] → 326,450 ops/sec [325,682..326,795] 🟢 +9.4%
spread into new array 322,973 ops/sec [322,189..325,706] → 326,840 ops/sec [316,009..327,553] ~ overlap (+1.2%) 198,255 ops/sec [197,261..199,495] → 208,159 ops/sec [207,051..208,521] 🟢 +5.0%
map over 50 elements 24,680 ops/sec [23,924..24,841] → 24,730 ops/sec [24,588..24,792] ~ overlap (+0.2%) 26,199 ops/sec [26,140..26,289] → 28,884 ops/sec [28,743..29,072] 🟢 +10.2%
filter over 50 elements 22,620 ops/sec [22,499..22,691] → 22,524 ops/sec [22,424..22,645] ~ overlap (-0.4%) 21,949 ops/sec [21,874..22,028] → 25,742 ops/sec [25,510..25,927] 🟢 +17.3%
reduce sum 50 elements 26,415 ops/sec [26,332..26,457] → 26,296 ops/sec [26,179..26,393] ~ overlap (-0.5%) 18,911 ops/sec [18,756..18,970] → 20,410 ops/sec [20,332..20,481] 🟢 +7.9%
forEach over 50 elements 23,347 ops/sec [23,196..23,411] → 23,229 ops/sec [22,945..23,265] ~ overlap (-0.5%) 26,998 ops/sec [26,790..27,150] → 30,068 ops/sec [29,993..30,086] 🟢 +11.4%
find in 50 elements 33,002 ops/sec [32,841..33,176] → 33,140 ops/sec [33,094..33,260] ~ overlap (+0.4%) 29,100 ops/sec [29,032..29,173] → 31,926 ops/sec [31,860..31,996] 🟢 +9.7%
sort 20 elements 12,297 ops/sec [12,234..12,359] → 12,475 ops/sec [12,356..12,569] ~ overlap (+1.5%) 13,078 ops/sec [13,036..13,084] → 14,363 ops/sec [14,343..14,397] 🟢 +9.8%
flat nested array 112,732 ops/sec [112,170..112,818] → 112,912 ops/sec [112,397..113,539] ~ overlap (+0.2%) 130,886 ops/sec [130,341..131,977] → 139,942 ops/sec [139,158..140,815] 🟢 +6.9%
flatMap 70,973 ops/sec [70,501..71,277] → 71,217 ops/sec [69,897..71,505] ~ overlap (+0.3%) 81,765 ops/sec [81,597..81,923] → 88,975 ops/sec [88,680..89,422] 🟢 +8.8%
map inside map (5x5) 20,106 ops/sec [20,071..20,252] → 20,119 ops/sec [19,966..20,215] ~ overlap (+0.1%) 22,243 ops/sec [22,169..22,371] → 24,441 ops/sec [24,387..24,522] 🟢 +9.9%
filter inside map (5x10) 15,328 ops/sec [15,312..15,366] → 15,407 ops/sec [15,251..15,553] ~ overlap (+0.5%) 15,548 ops/sec [15,463..15,747] → 18,056 ops/sec [18,002..18,092] 🟢 +16.1%
reduce inside map (5x10) 18,887 ops/sec [18,828..18,979] → 18,951 ops/sec [18,862..18,988] ~ overlap (+0.3%) 15,664 ops/sec [15,617..15,706] → 17,363 ops/sec [17,306..17,475] 🟢 +10.9%
forEach inside forEach (5x10) 16,641 ops/sec [16,595..16,735] → 16,753 ops/sec [16,681..16,882] ~ overlap (+0.7%) 16,488 ops/sec [16,424..16,576] → 18,265 ops/sec [18,228..18,356] 🟢 +10.8%
find inside some (10x10) 13,258 ops/sec [13,034..13,326] → 13,449 ops/sec [13,376..13,531] 🟢 +1.4% 12,248 ops/sec [12,219..12,284] → 13,796 ops/sec [13,768..13,839] 🟢 +12.6%
map+filter chain nested (5x20) 5,112 ops/sec [5,096..5,135] → 5,130 ops/sec [5,060..5,133] ~ overlap (+0.3%) 5,172 ops/sec [5,139..5,213] → 5,891 ops/sec [5,879..5,895] 🟢 +13.9%
reduce flatten (10x5) 37,307 ops/sec [37,096..37,763] → 37,302 ops/sec [37,174..37,603] ~ overlap (-0.0%) 17,183 ops/sec [17,105..17,220] → 19,227 ops/sec [19,166..19,347] 🟢 +11.9%
async-await.js — Interp: 🟢 1, 🔴 2, 3 unch. · avg -0.4% · Bytecode: 🟢 6 · avg +8.8%
Benchmark Interpreted Δ Bytecode Δ
single await 370,410 ops/sec [368,858..372,635] → 366,393 ops/sec [366,045..366,935] 🔴 -1.1% 386,488 ops/sec [385,601..387,930] → 433,280 ops/sec [432,738..435,516] 🟢 +12.1%
multiple awaits 167,100 ops/sec [165,135..167,390] → 164,844 ops/sec [164,503..165,374] ~ overlap (-1.4%) 175,905 ops/sec [175,281..177,554] → 190,748 ops/sec [190,034..190,985] 🟢 +8.4%
await non-Promise value 801,670 ops/sec [798,793..816,181] → 837,969 ops/sec [833,419..844,667] 🟢 +4.5% 1,081,626 ops/sec [1,079,299..1,084,386] → 1,145,466 ops/sec [1,144,389..1,146,636] 🟢 +5.9%
await with try/catch 363,152 ops/sec [361,959..363,968] → 357,369 ops/sec [355,801..359,380] 🔴 -1.6% 390,422 ops/sec [389,906..393,719] → 427,591 ops/sec [426,634..431,397] 🟢 +9.5%
await Promise.all 50,826 ops/sec [49,712..51,193] → 49,961 ops/sec [49,727..50,250] ~ overlap (-1.7%) 51,098 ops/sec [50,920..51,259] → 55,371 ops/sec [55,239..55,503] 🟢 +8.4%
nested async function call 190,754 ops/sec [188,912..191,165] → 188,637 ops/sec [188,430..189,052] ~ overlap (-1.1%) 225,235 ops/sec [224,107..226,779] → 244,804 ops/sec [244,127..245,525] 🟢 +8.7%
classes.js — Interp: 🟢 23, 8 unch. · avg +2.0% · Bytecode: 🟢 20, 🔴 1, 10 unch. · avg +9.3%
Benchmark Interpreted Δ Bytecode Δ
simple class new 135,104 ops/sec [132,374..136,252] → 136,956 ops/sec [136,379..137,441] 🟢 +1.4% 203,048 ops/sec [201,453..205,325] → 224,265 ops/sec [224,038..224,507] 🟢 +10.4%
class with defaults 107,565 ops/sec [107,025..108,136] → 108,640 ops/sec [107,963..109,200] ~ overlap (+1.0%) 141,354 ops/sec [140,309..142,532] → 153,257 ops/sec [152,800..153,794] 🟢 +8.4%
50 instances via Array.from 5,684 ops/sec [5,681..5,690] → 5,764 ops/sec [5,751..5,776] 🟢 +1.4% 7,539 ops/sec [7,451..7,599] → 8,606 ops/sec [8,575..8,615] 🟢 +14.1%
instance method call 68,555 ops/sec [68,386..68,714] → 70,861 ops/sec [70,663..71,049] 🟢 +3.4% 91,219 ops/sec [90,957..91,337] → 102,658 ops/sec [102,432..102,795] 🟢 +12.5%
static method call 109,958 ops/sec [109,317..110,069] → 111,354 ops/sec [110,682..111,959] 🟢 +1.3% 180,962 ops/sec [179,193..181,819] → 204,780 ops/sec [203,669..205,734] 🟢 +13.2%
single-level inheritance 53,620 ops/sec [53,516..54,234] → 55,352 ops/sec [55,244..55,640] 🟢 +3.2% 73,171 ops/sec [72,333..73,497] → 82,926 ops/sec [82,285..83,550] 🟢 +13.3%
two-level inheritance 45,694 ops/sec [45,523..45,938] → 47,410 ops/sec [47,342..47,444] 🟢 +3.8% 59,021 ops/sec [57,771..60,321] → 66,569 ops/sec [65,802..67,608] 🟢 +12.8%
private field access 68,470 ops/sec [68,257..69,022] → 70,311 ops/sec [70,042..70,492] 🟢 +2.7% 89,211 ops/sec [88,058..89,957] → 98,132 ops/sec [97,500..98,591] 🟢 +10.0%
private methods 76,488 ops/sec [75,821..77,079] → 77,925 ops/sec [77,712..78,079] 🟢 +1.9% 98,207 ops/sec [97,714..98,583] → 108,402 ops/sec [106,146..109,115] 🟢 +10.4%
getter/setter access 73,745 ops/sec [73,419..74,369] → 74,759 ops/sec [74,678..74,951] 🟢 +1.4% 101,848 ops/sec [100,621..102,156] → 113,375 ops/sec [112,687..113,934] 🟢 +11.3%
class decorator (identity) 90,465 ops/sec [88,323..91,277] → 92,468 ops/sec [91,802..93,008] 🟢 +2.2% 103,360 ops/sec [102,639..105,403] → 116,718 ops/sec [114,143..120,676] 🟢 +12.9%
class decorator (wrapping) 53,877 ops/sec [53,629..54,328] → 53,914 ops/sec [53,522..54,377] ~ overlap (+0.1%) 59,173 ops/sec [57,013..60,879] → 63,729 ops/sec [62,902..65,852] 🟢 +7.7%
identity method decorator 63,738 ops/sec [63,061..64,511] → 65,179 ops/sec [64,502..65,652] ~ overlap (+2.3%) 90,075 ops/sec [84,340..95,446] → 100,067 ops/sec [94,205..103,611] ~ overlap (+11.1%)
wrapping method decorator 51,336 ops/sec [50,550..51,435] → 51,490 ops/sec [51,156..51,711] ~ overlap (+0.3%) 62,187 ops/sec [59,475..66,312] → 65,954 ops/sec [63,666..68,422] ~ overlap (+6.1%)
stacked method decorators (x3) 35,651 ops/sec [35,511..35,844] → 35,517 ops/sec [35,359..35,733] ~ overlap (-0.4%) 43,898 ops/sec [41,695..47,562] → 49,168 ops/sec [46,081..50,755] ~ overlap (+12.0%)
identity field decorator 68,310 ops/sec [67,702..68,601] → 70,680 ops/sec [69,935..71,402] 🟢 +3.5% 74,088 ops/sec [71,389..77,654] → 81,681 ops/sec [79,744..83,793] 🟢 +10.2%
field initializer decorator 58,296 ops/sec [58,043..58,721] → 58,821 ops/sec [58,760..58,890] 🟢 +0.9% 64,807 ops/sec [61,615..68,756] → 71,862 ops/sec [67,165..77,434] ~ overlap (+10.9%)
getter decorator (identity) 61,911 ops/sec [61,751..62,178] → 63,745 ops/sec [63,417..64,202] 🟢 +3.0% 73,947 ops/sec [72,126..76,321] → 79,905 ops/sec [78,467..82,393] 🟢 +8.1%
setter decorator (identity) 52,621 ops/sec [52,053..52,731] → 53,931 ops/sec [53,804..54,128] 🟢 +2.5% 60,576 ops/sec [60,218..61,714] → 63,949 ops/sec [63,613..65,941] 🟢 +5.6%
static method decorator 68,358 ops/sec [68,012..68,581] → 68,578 ops/sec [68,488..69,087] ~ overlap (+0.3%) 82,156 ops/sec [78,591..86,552] → 93,432 ops/sec [88,161..97,778] 🟢 +13.7%
static field decorator 80,172 ops/sec [79,082..80,704] → 81,257 ops/sec [80,952..81,581] 🟢 +1.4% 88,161 ops/sec [86,135..95,258] → 88,279 ops/sec [86,717..92,373] ~ overlap (+0.1%)
private method decorator 51,858 ops/sec [51,618..52,239] → 53,547 ops/sec [53,290..53,648] 🟢 +3.3% 70,006 ops/sec [67,579..73,815] → 87,817 ops/sec [83,708..91,105] 🟢 +25.4%
private field decorator 56,229 ops/sec [56,203..56,449] → 57,386 ops/sec [57,176..57,684] 🟢 +2.1% 61,785 ops/sec [60,758..64,543] → 74,820 ops/sec [68,405..78,922] 🟢 +21.1%
plain auto-accessor (no decorator) 98,314 ops/sec [96,963..101,056] → 100,396 ops/sec [98,625..102,982] ~ overlap (+2.1%) 100,046 ops/sec [94,291..104,946] → 100,768 ops/sec [99,081..102,054] ~ overlap (+0.7%)
auto-accessor with decorator 53,745 ops/sec [52,414..54,951] → 54,217 ops/sec [53,295..55,894] ~ overlap (+0.9%) 57,247 ops/sec [56,247..63,715] → 63,378 ops/sec [58,895..68,185] ~ overlap (+10.7%)
decorator writing metadata 43,345 ops/sec [42,654..43,403] → 43,546 ops/sec [43,404..43,711] 🟢 +0.5% 50,642 ops/sec [49,687..54,908] → 49,987 ops/sec [47,826..53,631] ~ overlap (-1.3%)
static getter read 121,081 ops/sec [119,639..122,376] → 124,876 ops/sec [123,704..125,351] 🟢 +3.1% 169,798 ops/sec [165,714..174,493] → 175,629 ops/sec [173,749..176,718] ~ overlap (+3.4%)
static getter/setter pair 93,312 ops/sec [93,268..93,592] → 96,406 ops/sec [95,089..97,133] 🟢 +3.3% 120,288 ops/sec [119,927..122,480] → 123,623 ops/sec [121,324..133,527] ~ overlap (+2.8%)
inherited static getter 71,937 ops/sec [71,130..72,395] → 73,667 ops/sec [73,370..74,135] 🟢 +2.4% 96,274 ops/sec [93,708..98,778] → 103,975 ops/sec [102,994..104,587] 🟢 +8.0%
inherited static setter 77,643 ops/sec [77,249..78,125] → 80,329 ops/sec [79,142..80,779] 🟢 +3.5% 103,051 ops/sec [101,666..103,494] → 99,096 ops/sec [97,465..101,136] 🔴 -3.8%
inherited static getter with this binding 66,631 ops/sec [66,149..66,996] → 69,160 ops/sec [68,641..69,597] 🟢 +3.8% 87,994 ops/sec [86,686..89,038] → 93,195 ops/sec [92,874..93,872] 🟢 +5.9%
closures.js — Interp: 🟢 7, 4 unch. · avg +2.3% · Bytecode: 🟢 10, 1 unch. · avg +10.0%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 130,737 ops/sec [129,657..131,227] → 133,645 ops/sec [133,040..134,503] 🟢 +2.2% 267,626 ops/sec [263,207..268,984] → 301,741 ops/sec [297,014..303,663] 🟢 +12.7%
closure over multiple variables 116,243 ops/sec [115,708..116,906] → 120,439 ops/sec [119,725..121,275] 🟢 +3.6% 191,091 ops/sec [189,191..196,351] → 206,893 ops/sec [205,706..214,196] 🟢 +8.3%
nested closures 122,814 ops/sec [122,124..123,736] → 126,311 ops/sec [125,756..126,842] 🟢 +2.8% 223,883 ops/sec [218,881..233,664] → 245,340 ops/sec [242,634..248,861] 🟢 +9.6%
function as argument 91,127 ops/sec [90,898..91,437] → 94,231 ops/sec [93,952..94,449] 🟢 +3.4% 241,893 ops/sec [239,949..243,123] → 275,134 ops/sec [273,483..275,497] 🟢 +13.7%
function returning function 114,437 ops/sec [113,899..115,079] → 118,090 ops/sec [117,182..118,708] 🟢 +3.2% 272,520 ops/sec [270,106..275,253] → 309,011 ops/sec [307,548..311,425] 🟢 +13.4%
compose two functions 69,324 ops/sec [68,469..69,516] → 71,653 ops/sec [70,920..71,983] 🟢 +3.4% 148,326 ops/sec [146,203..151,586] → 162,250 ops/sec [161,591..163,330] 🟢 +9.4%
fn.call 148,959 ops/sec [145,195..150,496] → 154,276 ops/sec [151,951..154,672] 🟢 +3.6% 227,001 ops/sec [222,041..229,807] → 228,285 ops/sec [227,651..229,401] ~ overlap (+0.6%)
fn.apply 108,878 ops/sec [107,609..109,419] → 110,543 ops/sec [108,973..110,942] ~ overlap (+1.5%) 156,630 ops/sec [156,456..157,458] → 173,675 ops/sec [173,163..174,282] 🟢 +10.9%
fn.bind 141,322 ops/sec [139,146..142,005] → 142,297 ops/sec [141,292..143,822] ~ overlap (+0.7%) 281,620 ops/sec [279,303..285,050] → 294,360 ops/sec [294,037..295,457] 🟢 +4.5%
recursive sum to 50 12,058 ops/sec [11,856..12,130] → 12,067 ops/sec [11,994..12,463] ~ overlap (+0.1%) 23,126 ops/sec [23,033..23,188] → 27,003 ops/sec [26,636..27,087] 🟢 +16.8%
recursive tree traversal 20,103 ops/sec [20,015..20,232] → 20,287 ops/sec [20,206..20,657] ~ overlap (+0.9%) 34,712 ops/sec [34,482..35,118] → 38,043 ops/sec [37,774..38,318] 🟢 +9.6%
collections.js — Interp: 🟢 9, 3 unch. · avg +2.2% · Bytecode: 🟢 9, 🔴 2, 1 unch. · avg +4.9%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 6,875 ops/sec [6,851..6,889] → 7,066 ops/sec [6,951..7,117] 🟢 +2.8% 7,090 ops/sec [7,052..7,115] → 7,581 ops/sec [7,566..7,587] 🟢 +6.9%
has lookup (50 elements) 87,441 ops/sec [87,087..87,594] → 89,550 ops/sec [89,108..89,986] 🟢 +2.4% 96,484 ops/sec [96,249..96,782] → 101,333 ops/sec [101,118..101,493] 🟢 +5.0%
delete elements 46,726 ops/sec [46,465..46,969] → 47,278 ops/sec [47,150..47,427] 🟢 +1.2% 48,599 ops/sec [48,444..48,724] → 50,765 ops/sec [50,697..50,799] 🟢 +4.5%
forEach iteration 15,294 ops/sec [15,192..15,368] → 15,556 ops/sec [15,437..15,655] 🟢 +1.7% 14,730 ops/sec [14,691..14,769] → 16,071 ops/sec [15,994..16,124] 🟢 +9.1%
spread to array 27,595 ops/sec [26,877..27,842] → 28,062 ops/sec [27,654..28,342] ~ overlap (+1.7%) 212,936 ops/sec [210,063..213,957] → 232,104 ops/sec [231,697..236,998] 🟢 +9.0%
deduplicate array 37,049 ops/sec [36,453..37,745] → 37,112 ops/sec [36,435..38,567] ~ overlap (+0.2%) 72,161 ops/sec [71,451..72,770] → 75,334 ops/sec [75,161..75,367] 🟢 +4.4%
set 50 entries 5,036 ops/sec [5,014..5,089] → 5,151 ops/sec [5,127..5,164] 🟢 +2.3% 5,333 ops/sec [5,316..5,396] → 5,735 ops/sec [5,718..5,789] 🟢 +7.5%
get lookup (50 entries) 85,036 ops/sec [84,645..85,176] → 87,537 ops/sec [86,779..87,956] 🟢 +2.9% 90,915 ops/sec [90,737..91,041] → 90,516 ops/sec [90,369..90,633] 🔴 -0.4%
has check 127,631 ops/sec [127,257..128,116] → 130,299 ops/sec [129,435..130,897] 🟢 +2.1% 140,350 ops/sec [140,271..140,378] → 142,361 ops/sec [142,280..142,506] 🟢 +1.4%
delete entries 45,222 ops/sec [44,988..45,354] → 46,879 ops/sec [46,220..47,203] 🟢 +3.7% 47,212 ops/sec [46,816..47,636] → 46,400 ops/sec [46,298..46,528] 🔴 -1.7%
forEach iteration 15,221 ops/sec [15,091..15,353] → 15,695 ops/sec [15,446..15,802] 🟢 +3.1% 14,902 ops/sec [14,776..14,964] → 16,957 ops/sec [16,927..16,976] 🟢 +13.8%
keys/values/entries 7,431 ops/sec [7,374..7,591] → 7,605 ops/sec [7,470..7,672] ~ overlap (+2.4%) 27,802 ops/sec [27,305..28,187] → 27,638 ops/sec [27,609..28,083] ~ overlap (-0.6%)
destructuring.js — Interp: 🟢 3, 🔴 5, 14 unch. · avg -0.1% · Bytecode: 🟢 22 · avg +6.2%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 363,088 ops/sec [360,835..367,014] → 368,452 ops/sec [365,963..371,634] ~ overlap (+1.5%) 272,236 ops/sec [269,632..274,900] → 287,994 ops/sec [287,211..289,297] 🟢 +5.8%
with rest element 238,123 ops/sec [223,023..241,065] → 240,272 ops/sec [235,487..242,824] ~ overlap (+0.9%) 214,320 ops/sec [212,790..215,761] → 223,615 ops/sec [221,477..224,446] 🟢 +4.3%
with defaults 368,484 ops/sec [363,091..373,623] → 383,047 ops/sec [379,242..383,729] 🟢 +4.0% 313,888 ops/sec [312,240..319,177] → 334,549 ops/sec [331,667..335,586] 🟢 +6.6%
skip elements 387,772 ops/sec [383,828..391,658] → 393,907 ops/sec [390,500..395,599] ~ overlap (+1.6%) 293,174 ops/sec [290,927..297,967] → 304,345 ops/sec [303,798..305,064] 🟢 +3.8%
nested array destructuring 168,330 ops/sec [166,883..168,469] → 168,174 ops/sec [165,525..168,487] ~ overlap (-0.1%) 93,105 ops/sec [92,138..94,272] → 95,712 ops/sec [95,073..96,095] 🟢 +2.8%
swap variables 482,394 ops/sec [475,038..486,083] → 497,336 ops/sec [492,506..500,523] 🟢 +3.1% 337,876 ops/sec [332,179..340,024] → 347,683 ops/sec [346,152..349,014] 🟢 +2.9%
simple object destructuring 297,327 ops/sec [295,117..299,041] → 295,997 ops/sec [293,862..297,804] ~ overlap (-0.4%) 407,335 ops/sec [404,825..408,916] → 427,241 ops/sec [424,192..428,867] 🟢 +4.9%
with defaults 330,843 ops/sec [328,073..332,843] → 334,156 ops/sec [331,411..341,157] ~ overlap (+1.0%) 538,949 ops/sec [535,826..539,821] → 593,321 ops/sec [592,007..593,448] 🟢 +10.1%
with renaming 315,707 ops/sec [315,405..316,044] → 317,279 ops/sec [310,385..322,316] ~ overlap (+0.5%) 444,636 ops/sec [441,883..447,019] → 467,066 ops/sec [464,482..469,223] 🟢 +5.0%
nested object destructuring 146,218 ops/sec [145,256..146,618] → 147,704 ops/sec [147,091..148,882] 🟢 +1.0% 194,188 ops/sec [193,477..195,305] → 201,964 ops/sec [201,107..202,738] 🟢 +4.0%
rest properties 178,671 ops/sec [176,852..180,000] → 180,844 ops/sec [179,431..183,001] ~ overlap (+1.2%) 189,843 ops/sec [188,250..191,088] → 195,502 ops/sec [194,478..195,762] 🟢 +3.0%
object parameter 91,731 ops/sec [91,060..92,202] → 92,775 ops/sec [92,121..94,413] ~ overlap (+1.1%) 144,814 ops/sec [143,959..147,426] → 157,993 ops/sec [156,941..158,640] 🟢 +9.1%
array parameter 120,227 ops/sec [119,525..120,990] → 118,465 ops/sec [118,266..118,635] 🔴 -1.5% 112,343 ops/sec [111,973..113,636] → 115,424 ops/sec [114,960..115,656] 🟢 +2.7%
mixed destructuring in map 32,331 ops/sec [32,055..32,515] → 32,283 ops/sec [32,195..32,382] ~ overlap (-0.2%) 33,411 ops/sec [33,229..33,525] → 36,488 ops/sec [36,338..36,626] 🟢 +9.2%
forEach with array destructuring 63,218 ops/sec [62,702..63,539] → 61,411 ops/sec [61,033..61,654] 🔴 -2.9% 42,486 ops/sec [42,389..42,581] → 44,856 ops/sec [44,575..45,046] 🟢 +5.6%
map with array destructuring 62,521 ops/sec [60,354..62,993] → 60,558 ops/sec [59,963..61,358] ~ overlap (-3.1%) 42,867 ops/sec [42,500..43,325] → 44,135 ops/sec [44,009..44,208] 🟢 +3.0%
filter with array destructuring 64,835 ops/sec [64,371..65,190] → 62,597 ops/sec [62,087..63,627] 🔴 -3.5% 46,781 ops/sec [46,484..46,867] → 50,897 ops/sec [50,754..51,269] 🟢 +8.8%
reduce with array destructuring 71,740 ops/sec [71,406..72,563] → 69,814 ops/sec [69,161..71,057] 🔴 -2.7% 46,943 ops/sec [46,696..47,276] → 49,722 ops/sec [49,215..49,966] 🟢 +5.9%
map with object destructuring 71,281 ops/sec [70,999..71,574] → 71,037 ops/sec [70,563..71,409] ~ overlap (-0.3%) 86,087 ops/sec [85,366..86,431] → 94,747 ops/sec [93,755..95,030] 🟢 +10.1%
map with nested destructuring 59,353 ops/sec [58,809..59,663] → 59,655 ops/sec [59,464..60,277] ~ overlap (+0.5%) 82,916 ops/sec [82,613..83,264] → 90,230 ops/sec [89,768..90,388] 🟢 +8.8%
map with rest in destructuring 41,700 ops/sec [40,565..41,945] → 40,302 ops/sec [40,188..40,487] 🔴 -3.4% 23,956 ops/sec [23,589..24,105] → 25,872 ops/sec [25,773..25,960] 🟢 +8.0%
map with defaults in destructuring 55,577 ops/sec [55,099..55,886] → 55,082 ops/sec [54,824..55,582] ~ overlap (-0.9%) 61,902 ops/sec [61,755..62,027] → 69,588 ops/sec [69,182..69,793] 🟢 +12.4%
fibonacci.js — Interp: 🟢 1, 7 unch. · avg +0.3% · Bytecode: 🟢 8 · avg +14.4%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 316 ops/sec [313..317] → 317 ops/sec [316..322] ~ overlap (+0.1%) 795 ops/sec [793..799] → 926 ops/sec [922..928] 🟢 +16.4%
recursive fib(20) 29 ops/sec [29..29] → 29 ops/sec [29..29] ~ overlap (-0.2%) 70 ops/sec [70..71] → 84 ops/sec [84..85] 🟢 +19.8%
recursive fib(15) typed 318 ops/sec [317..320] → 325 ops/sec [323..326] 🟢 +2.2% 841 ops/sec [838..843] → 955 ops/sec [954..956] 🟢 +13.5%
recursive fib(20) typed 29 ops/sec [29..29] → 29 ops/sec [29..29] ~ overlap (-0.6%) 75 ops/sec [75..75] → 87 ops/sec [87..87] 🟢 +16.5%
iterative fib(20) via reduce 11,851 ops/sec [11,791..12,012] → 11,856 ops/sec [11,807..11,874] ~ overlap (+0.0%) 17,644 ops/sec [17,549..17,875] → 18,667 ops/sec [18,552..18,693] 🟢 +5.8%
iterator fib(20) 9,299 ops/sec [9,208..9,371] → 9,401 ops/sec [9,346..9,449] ~ overlap (+1.1%) 17,457 ops/sec [17,094..17,772] → 19,806 ops/sec [19,417..19,945] 🟢 +13.5%
iterator fib(20) via Iterator.from + take 15,198 ops/sec [15,131..15,280] → 15,227 ops/sec [15,142..15,278] ~ overlap (+0.2%) 19,740 ops/sec [19,648..19,909] → 21,663 ops/sec [21,445..21,710] 🟢 +9.7%
iterator fib(20) last value via reduce 11,284 ops/sec [11,187..11,370] → 11,228 ops/sec [11,201..11,272] ~ overlap (-0.5%) 13,157 ops/sec [13,066..13,220] → 15,830 ops/sec [15,751..15,870] 🟢 +20.3%
for-of.js — Interp: 7 unch. · avg +0.2% · Bytecode: 🟢 7 · avg +7.6%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 45,407 ops/sec [44,372..45,783] → 45,095 ops/sec [44,988..45,393] ~ overlap (-0.7%) 163,323 ops/sec [161,671..163,688] → 183,080 ops/sec [182,792..183,575] 🟢 +12.1%
for...of with 100-element array 5,160 ops/sec [4,964..5,180] → 5,186 ops/sec [5,157..5,212] ~ overlap (+0.5%) 18,043 ops/sec [17,975..18,081] → 18,816 ops/sec [18,703..18,921] 🟢 +4.3%
for...of with string (10 chars) 32,957 ops/sec [32,550..33,215] → 32,931 ops/sec [32,417..33,093] ~ overlap (-0.1%) 82,589 ops/sec [82,034..83,398] → 91,689 ops/sec [91,384..91,964] 🟢 +11.0%
for...of with Set (10 elements) 45,980 ops/sec [45,179..46,254] → 45,291 ops/sec [45,140..45,626] ~ overlap (-1.5%) 151,028 ops/sec [150,188..152,027] → 164,093 ops/sec [163,704..166,272] 🟢 +8.7%
for...of with Map entries (10 entries) 28,522 ops/sec [28,358..28,691] → 28,477 ops/sec [28,164..28,674] ~ overlap (-0.2%) 28,355 ops/sec [28,059..28,807] → 30,217 ops/sec [30,097..30,399] 🟢 +6.6%
for...of with destructuring 37,435 ops/sec [37,094..38,614] → 38,287 ops/sec [38,121..38,378] ~ overlap (+2.3%) 36,079 ops/sec [35,713..36,247] → 38,167 ops/sec [38,114..38,702] 🟢 +5.8%
for-await-of with sync array 42,916 ops/sec [42,377..43,569] → 43,281 ops/sec [43,025..43,403] ~ overlap (+0.9%) 126,900 ops/sec [126,701..127,237] → 132,679 ops/sec [132,241..133,796] 🟢 +4.6%
helpers/bench-module.js — Interp: 0 · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
iterators.js — Interp: 🟢 4, 16 unch. · avg +0.7% · Bytecode: 🟢 20 · avg +12.7%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 15,113 ops/sec [15,009..15,228] → 15,159 ops/sec [15,123..15,168] ~ overlap (+0.3%) 19,280 ops/sec [19,149..19,369] → 22,580 ops/sec [22,528..22,709] 🟢 +17.1%
Iterator.from({next}).toArray() — 50 elements 6,543 ops/sec [6,484..6,575] → 6,505 ops/sec [6,489..6,517] ~ overlap (-0.6%) 8,842 ops/sec [8,762..8,887] → 9,768 ops/sec [9,747..9,796] 🟢 +10.5%
spread pre-wrapped iterator — 20 elements 11,286 ops/sec [11,231..11,296] → 11,350 ops/sec [11,231..11,416] ~ overlap (+0.6%) 18,735 ops/sec [18,685..18,845] → 22,148 ops/sec [21,654..22,170] 🟢 +18.2%
Iterator.from({next}).forEach — 50 elements 4,488 ops/sec [4,445..4,523] → 4,546 ops/sec [4,507..4,554] ~ overlap (+1.3%) 5,562 ops/sec [5,521..5,584] → 5,914 ops/sec [5,895..5,929] 🟢 +6.3%
Iterator.from({next}).reduce — 50 elements 4,463 ops/sec [4,430..4,505] → 4,517 ops/sec [4,512..4,560] 🟢 +1.2% 5,150 ops/sec [5,117..5,190] → 6,069 ops/sec [6,041..6,083] 🟢 +17.9%
wrap array iterator 153,770 ops/sec [151,384..156,276] → 155,327 ops/sec [154,490..157,013] ~ overlap (+1.0%) 187,910 ops/sec [186,510..189,403] → 195,059 ops/sec [194,008..195,689] 🟢 +3.8%
wrap plain {next()} object 10,392 ops/sec [10,221..10,445] → 10,485 ops/sec [10,383..10,706] ~ overlap (+0.9%) 13,394 ops/sec [13,328..13,533] → 15,698 ops/sec [15,639..15,734] 🟢 +17.2%
map + toArray (50 elements) 4,497 ops/sec [4,462..4,532] → 4,524 ops/sec [4,356..4,552] ~ overlap (+0.6%) 5,736 ops/sec [5,728..5,758] → 6,609 ops/sec [6,567..6,629] 🟢 +15.2%
filter + toArray (50 elements) 4,395 ops/sec [4,385..4,414] → 4,345 ops/sec [4,291..4,404] ~ overlap (-1.2%) 5,699 ops/sec [5,639..5,753] → 6,565 ops/sec [6,552..6,582] 🟢 +15.2%
take(10) + toArray (50 element source) 27,291 ops/sec [27,010..27,461] → 27,185 ops/sec [27,059..27,503] ~ overlap (-0.4%) 35,868 ops/sec [35,651..36,011] → 39,429 ops/sec [39,166..39,637] 🟢 +9.9%
drop(40) + toArray (50 element source) 6,570 ops/sec [6,494..6,663] → 6,567 ops/sec [6,522..6,594] ~ overlap (-0.0%) 8,437 ops/sec [8,379..8,477] → 9,923 ops/sec [9,886..9,950] 🟢 +17.6%
chained map + filter + take (100 element source) 8,064 ops/sec [8,013..8,124] → 8,149 ops/sec [8,068..8,160] ~ overlap (+1.0%) 10,279 ops/sec [9,898..10,303] → 11,156 ops/sec [11,064..11,209] 🟢 +8.5%
some + every (50 elements) 2,588 ops/sec [2,571..2,607] → 2,609 ops/sec [2,546..2,640] ~ overlap (+0.8%) 3,314 ops/sec [3,290..3,322] → 3,884 ops/sec [3,852..3,919] 🟢 +17.2%
find (50 elements) 5,669 ops/sec [5,611..5,737] → 5,666 ops/sec [5,640..5,695] ~ overlap (-0.0%) 7,005 ops/sec [6,969..7,016] → 7,879 ops/sec [7,875..7,906] 🟢 +12.5%
array.values().map().filter().toArray() 7,846 ops/sec [7,795..7,881] → 7,836 ops/sec [7,727..7,892] ~ overlap (-0.1%) 9,436 ops/sec [9,367..9,455] → 10,495 ops/sec [10,412..10,510] 🟢 +11.2%
array.values().take(5).toArray() 200,714 ops/sec [199,641..202,046] → 208,536 ops/sec [207,125..209,541] 🟢 +3.9% 231,089 ops/sec [228,866..231,456] → 254,537 ops/sec [252,618..255,011] 🟢 +10.1%
array.values().drop(45).toArray() 186,075 ops/sec [183,700..187,754] → 192,258 ops/sec [190,367..192,977] 🟢 +3.3% 211,958 ops/sec [208,898..215,084] → 233,742 ops/sec [233,330..234,142] 🟢 +10.3%
map.entries() chained helpers 9,857 ops/sec [9,782..9,893] → 9,978 ops/sec [9,921..10,021] 🟢 +1.2% 5,978 ops/sec [5,947..6,034] → 6,228 ops/sec [6,211..6,270] 🟢 +4.2%
set.values() chained helpers 16,590 ops/sec [16,118..16,739] → 16,768 ops/sec [16,335..16,929] ~ overlap (+1.1%) 18,076 ops/sec [17,870..18,410] → 21,237 ops/sec [19,557..21,296] 🟢 +17.5%
string iterator map + toArray 13,187 ops/sec [13,118..13,261] → 13,194 ops/sec [13,154..13,228] ~ overlap (+0.1%) 13,856 ops/sec [13,729..13,941] → 15,681 ops/sec [15,674..15,699] 🟢 +13.2%
json.js — Interp: 🟢 8, 12 unch. · avg +1.0% · Bytecode: 🟢 20 · avg +11.0%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 165,577 ops/sec [154,769..166,526] → 166,930 ops/sec [166,692..169,663] 🟢 +0.8% 175,185 ops/sec [173,598..175,737] → 194,139 ops/sec [193,215..195,743] 🟢 +10.8%
parse nested object 99,931 ops/sec [97,369..101,248] → 102,751 ops/sec [102,417..102,845] 🟢 +2.8% 104,357 ops/sec [103,548..106,624] → 112,252 ops/sec [111,737..112,498] 🟢 +7.6%
parse array of objects 59,023 ops/sec [58,046..59,576] → 59,758 ops/sec [59,527..60,501] ~ overlap (+1.2%) 63,273 ops/sec [62,863..63,614] → 71,842 ops/sec [71,274..72,076] 🟢 +13.5%
parse large flat object 66,245 ops/sec [64,582..66,456] → 66,939 ops/sec [66,616..67,519] 🟢 +1.0% 69,280 ops/sec [68,869..69,726] → 77,027 ops/sec [76,581..77,648] 🟢 +11.2%
parse mixed types 76,134 ops/sec [75,175..76,671] → 77,029 ops/sec [76,029..77,720] ~ overlap (+1.2%) 81,637 ops/sec [77,963..82,029] → 89,070 ops/sec [88,963..89,377] 🟢 +9.1%
stringify simple object 162,417 ops/sec [161,430..162,767] → 166,080 ops/sec [165,481..167,091] 🟢 +2.3% 172,561 ops/sec [170,531..178,022] → 199,310 ops/sec [198,055..199,610] 🟢 +15.5%
stringify nested object 92,108 ops/sec [91,925..92,507] → 93,877 ops/sec [92,841..94,437] 🟢 +1.9% 96,588 ops/sec [95,577..97,975] → 105,427 ops/sec [104,997..106,247] 🟢 +9.2%
stringify array of objects 43,744 ops/sec [43,195..43,843] → 45,018 ops/sec [44,883..45,149] 🟢 +2.9% 44,821 ops/sec [44,549..44,924] → 49,409 ops/sec [49,224..49,599] 🟢 +10.2%
stringify mixed types 68,162 ops/sec [68,089..69,010] → 69,793 ops/sec [69,688..69,822] 🟢 +2.4% 69,671 ops/sec [69,574..70,031] → 77,753 ops/sec [77,496..78,073] 🟢 +11.6%
reviver doubles numbers 46,796 ops/sec [46,540..48,227] → 47,406 ops/sec [46,033..47,755] ~ overlap (+1.3%) 52,305 ops/sec [50,624..53,356] → 58,520 ops/sec [56,042..60,967] 🟢 +11.9%
reviver filters properties 40,219 ops/sec [39,996..40,385] → 39,744 ops/sec [39,648..40,323] ~ overlap (-1.2%) 41,549 ops/sec [41,214..42,341] → 47,327 ops/sec [46,645..47,388] 🟢 +13.9%
reviver on nested object 51,154 ops/sec [50,823..51,684] → 50,947 ops/sec [50,613..51,543] ~ overlap (-0.4%) 54,066 ops/sec [53,244..55,001] → 60,675 ops/sec [59,616..61,168] 🟢 +12.2%
reviver on array 31,821 ops/sec [31,684..31,893] → 31,658 ops/sec [31,299..32,026] ~ overlap (-0.5%) 32,300 ops/sec [31,809..32,590] → 35,966 ops/sec [35,640..36,007] 🟢 +11.4%
replacer function doubles numbers 41,460 ops/sec [41,259..41,720] → 41,411 ops/sec [41,293..41,543] ~ overlap (-0.1%) 46,026 ops/sec [45,830..46,286] → 51,855 ops/sec [51,572..52,150] 🟢 +12.7%
replacer function excludes properties 54,466 ops/sec [54,238..54,837] → 54,697 ops/sec [52,466..55,428] ~ overlap (+0.4%) 55,740 ops/sec [54,915..55,776] → 63,126 ops/sec [62,053..63,283] 🟢 +13.2%
array replacer (allowlist) 96,045 ops/sec [95,221..96,461] → 95,129 ops/sec [94,747..95,796] ~ overlap (-1.0%) 100,524 ops/sec [99,809..101,418] → 109,198 ops/sec [108,701..109,504] 🟢 +8.6%
stringify with 2-space indent 77,077 ops/sec [69,979..77,559] → 77,453 ops/sec [76,715..77,980] ~ overlap (+0.5%) 81,553 ops/sec [79,921..83,160] → 88,037 ops/sec [87,598..88,253] 🟢 +8.0%
stringify with tab indent 76,921 ops/sec [75,973..77,940] → 77,965 ops/sec [77,625..78,029] ~ overlap (+1.4%) 81,107 ops/sec [80,807..81,450] → 88,592 ops/sec [88,334..88,676] 🟢 +9.2%
parse then stringify 48,240 ops/sec [48,096..48,513] → 48,440 ops/sec [47,960..48,994] ~ overlap (+0.4%) 51,353 ops/sec [51,090..51,663] → 56,684 ops/sec [56,600..56,745] 🟢 +10.4%
stringify then parse 29,402 ops/sec [29,275..29,507] → 30,114 ops/sec [29,914..30,388] 🟢 +2.4% 31,657 ops/sec [31,466..31,891] → 34,708 ops/sec [34,283..34,790] 🟢 +9.6%
jsx.jsx — Interp: 🟢 2, 🔴 5, 14 unch. · avg -0.3% · Bytecode: 🟢 13, 🔴 1, 7 unch. · avg +2.3%
Benchmark Interpreted Δ Bytecode Δ
simple element 205,965 ops/sec [204,791..207,129] → 209,150 ops/sec [207,604..210,312] 🟢 +1.5% 317,249 ops/sec [311,409..321,591] → 321,127 ops/sec [319,820..323,231] ~ overlap (+1.2%)
self-closing element 219,388 ops/sec [218,276..220,446] → 220,651 ops/sec [217,745..223,609] ~ overlap (+0.6%) 348,608 ops/sec [344,999..349,903] → 353,097 ops/sec [351,240..358,512] 🟢 +1.3%
element with string attribute 174,089 ops/sec [173,845..175,902] → 175,041 ops/sec [174,711..178,357] ~ overlap (+0.5%) 249,381 ops/sec [248,921..249,757] → 248,301 ops/sec [247,496..249,587] ~ overlap (-0.4%)
element with multiple attributes 155,347 ops/sec [154,982..156,254] → 153,546 ops/sec [151,575..155,179] ~ overlap (-1.2%) 198,199 ops/sec [195,464..200,752] → 201,517 ops/sec [200,700..202,534] ~ overlap (+1.7%)
element with expression attribute 165,472 ops/sec [164,473..166,083] → 165,044 ops/sec [163,928..166,359] ~ overlap (-0.3%) 246,889 ops/sec [245,203..249,633] → 250,691 ops/sec [249,835..250,767] 🟢 +1.5%
text child 210,668 ops/sec [207,404..211,178] → 208,034 ops/sec [207,492..214,489] ~ overlap (-1.3%) 310,708 ops/sec [308,219..314,986] → 325,562 ops/sec [323,560..327,452] 🟢 +4.8%
expression child 201,100 ops/sec [179,174..204,615] → 206,151 ops/sec [201,691..207,616] ~ overlap (+2.5%) 312,575 ops/sec [311,011..315,087] → 322,382 ops/sec [321,349..322,914] 🟢 +3.1%
mixed text and expression 190,647 ops/sec [189,397..192,095] → 192,383 ops/sec [191,196..194,189] ~ overlap (+0.9%) 280,726 ops/sec [278,341..285,194] → 292,516 ops/sec [290,137..293,036] 🟢 +4.2%
nested elements (3 levels) 81,300 ops/sec [80,990..81,752] → 78,336 ops/sec [78,187..78,938] 🔴 -3.6% 120,004 ops/sec [119,360..120,408] → 116,423 ops/sec [115,150..117,553] 🔴 -3.0%
sibling children 59,472 ops/sec [59,037..59,787] → 58,305 ops/sec [57,587..58,467] 🔴 -2.0% 85,415 ops/sec [83,364..86,320] → 87,612 ops/sec [86,907..88,265] 🟢 +2.6%
component element 148,981 ops/sec [148,166..149,001] → 149,412 ops/sec [147,876..149,914] ~ overlap (+0.3%) 222,300 ops/sec [220,458..223,054] → 221,788 ops/sec [218,543..222,387] ~ overlap (-0.2%)
component with children 91,138 ops/sec [90,274..91,732] → 91,113 ops/sec [90,926..91,592] ~ overlap (-0.0%) 135,416 ops/sec [133,251..136,810] → 141,244 ops/sec [140,222..141,695] 🟢 +4.3%
dotted component 122,638 ops/sec [121,215..123,967] → 123,184 ops/sec [121,882..124,424] ~ overlap (+0.4%) 172,784 ops/sec [172,299..174,404] → 173,921 ops/sec [172,852..174,851] ~ overlap (+0.7%)
empty fragment 220,717 ops/sec [219,932..221,709] → 214,614 ops/sec [213,346..215,610] 🔴 -2.8% 362,323 ops/sec [361,016..365,109] → 378,592 ops/sec [376,518..379,717] 🟢 +4.5%
fragment with children 58,931 ops/sec [58,494..59,213] → 57,607 ops/sec [57,465..57,789] 🔴 -2.2% 88,529 ops/sec [88,349..89,851] → 89,645 ops/sec [89,391..89,767] ~ overlap (+1.3%)
spread attributes 107,062 ops/sec [106,115..108,075] → 108,252 ops/sec [107,022..110,776] ~ overlap (+1.1%) 130,216 ops/sec [129,744..133,652] → 138,861 ops/sec [138,545..140,083] 🟢 +6.6%
spread with overrides 93,440 ops/sec [91,660..94,602] → 94,379 ops/sec [93,147..94,785] ~ overlap (+1.0%) 113,921 ops/sec [113,338..114,437] → 116,396 ops/sec [116,231..116,498] 🟢 +2.2%
shorthand props 156,866 ops/sec [155,654..158,144] → 155,710 ops/sec [153,968..157,096] ~ overlap (-0.7%) 232,253 ops/sec [229,101..236,434] → 241,883 ops/sec [239,832..242,054] 🟢 +4.1%
nav bar structure 27,633 ops/sec [27,513..27,783] → 27,148 ops/sec [26,979..27,304] 🔴 -1.8% 39,391 ops/sec [38,542..40,038] → 39,417 ops/sec [39,282..39,612] ~ overlap (+0.1%)
card component tree 31,172 ops/sec [31,028..31,374] → 31,747 ops/sec [31,555..31,934] 🟢 +1.8% 43,488 ops/sec [43,120..43,872] → 45,211 ops/sec [44,887..45,415] 🟢 +4.0%
10 list items via Array.from 14,289 ops/sec [14,188..14,388] → 14,167 ops/sec [14,068..14,298] ~ overlap (-0.9%) 18,359 ops/sec [18,286..18,478] → 18,875 ops/sec [18,673..18,915] 🟢 +2.8%
modules.js — Interp: 🟢 2, 7 unch. · avg +1.2% · Bytecode: 🟢 9 · avg +16.2%
Benchmark Interpreted Δ Bytecode Δ
call imported function 457,548 ops/sec [455,103..458,287] → 461,135 ops/sec [458,588..470,155] 🟢 +0.8% 631,079 ops/sec [629,429..632,734] → 637,464 ops/sec [634,790..639,681] 🟢 +1.0%
call two imported functions 255,011 ops/sec [252,674..255,519] → 257,164 ops/sec [255,396..261,467] ~ overlap (+0.8%) 350,452 ops/sec [348,784..352,842] → 357,016 ops/sec [356,523..357,792] 🟢 +1.9%
read imported constant 1,630,012 ops/sec [1,626,234..1,643,598] → 1,663,429 ops/sec [1,637,920..1,683,011] ~ overlap (+2.1%) 3,367,163 ops/sec [3,362,858..3,373,868] → 4,025,323 ops/sec [4,019,849..4,029,796] 🟢 +19.5%
read imported string 1,638,066 ops/sec [1,634,525..1,652,591] → 1,638,669 ops/sec [1,622,875..1,647,490] ~ overlap (+0.0%) 3,303,515 ops/sec [3,290,072..3,309,822] → 4,034,238 ops/sec [4,029,457..4,039,120] 🟢 +22.1%
read JSON string property 1,660,045 ops/sec [1,623,842..1,670,216] → 1,669,244 ops/sec [1,655,710..1,691,067] ~ overlap (+0.6%) 3,360,374 ops/sec [3,354,372..3,369,376] → 4,011,112 ops/sec [4,007,842..4,013,030] 🟢 +19.4%
read JSON number property 1,654,774 ops/sec [1,633,878..1,669,715] → 1,650,037 ops/sec [1,642,630..1,666,440] ~ overlap (-0.3%) 3,283,928 ops/sec [3,241,050..3,302,056] → 4,028,367 ops/sec [4,025,625..4,033,787] 🟢 +22.7%
read JSON boolean property 1,663,018 ops/sec [1,632,795..1,676,253] → 1,647,311 ops/sec [1,640,868..1,701,954] ~ overlap (-0.9%) 3,360,179 ops/sec [3,349,940..3,373,054] → 4,019,652 ops/sec [4,018,634..4,025,071] 🟢 +19.6%
read JSON array property 1,640,719 ops/sec [1,627,078..1,667,536] → 1,662,406 ops/sec [1,655,105..1,693,700] ~ overlap (+1.3%) 3,350,430 ops/sec [3,338,686..3,380,284] → 4,028,647 ops/sec [4,022,987..4,034,305] 🟢 +20.2%
read multiple JSON properties 917,647 ops/sec [912,856..919,885] → 975,595 ops/sec [968,260..990,828] 🟢 +6.3% 3,051,913 ops/sec [3,046,836..3,057,697] → 3,629,058 ops/sec [3,628,813..3,631,564] 🟢 +18.9%
numbers.js — Interp: 🟢 6, 🔴 1, 4 unch. · avg +2.2% · Bytecode: 🟢 10, 1 unch. · avg +13.5%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 504,959 ops/sec [499,392..510,240] → 510,015 ops/sec [502,851..513,086] ~ overlap (+1.0%) 1,464,006 ops/sec [1,463,416..1,465,971] → 1,735,360 ops/sec [1,731,207..1,738,954] 🟢 +18.5%
floating point arithmetic 578,948 ops/sec [578,561..581,837] → 602,888 ops/sec [596,153..606,672] 🟢 +4.1% 933,912 ops/sec [927,847..939,649] → 1,274,043 ops/sec [1,266,638..1,280,049] 🟢 +36.4%
number coercion 180,288 ops/sec [178,582..181,275] → 181,113 ops/sec [179,377..185,384] ~ overlap (+0.5%) 220,922 ops/sec [219,997..222,224] → 247,745 ops/sec [247,389..248,541] 🟢 +12.1%
toFixed 101,946 ops/sec [101,846..102,151] → 100,230 ops/sec [99,962..100,716] 🔴 -1.7% 108,386 ops/sec [107,664..109,273] → 124,652 ops/sec [123,878..125,028] 🟢 +15.0%
toString 152,332 ops/sec [151,582..153,464] → 150,232 ops/sec [148,253..152,570] ~ overlap (-1.4%) 182,007 ops/sec [180,656..184,455] → 195,517 ops/sec [193,783..195,997] 🟢 +7.4%
valueOf 214,147 ops/sec [212,709..214,643] → 222,915 ops/sec [219,735..223,707] 🟢 +4.1% 257,628 ops/sec [256,917..259,049] → 287,099 ops/sec [285,825..288,848] 🟢 +11.4%
toPrecision 142,893 ops/sec [142,284..143,665] → 140,248 ops/sec [138,871..142,448] ~ overlap (-1.9%) 162,945 ops/sec [161,533..163,976] → 163,931 ops/sec [161,790..165,180] ~ overlap (+0.6%)
Number.isNaN 304,168 ops/sec [302,571..305,877] → 311,002 ops/sec [309,360..313,711] 🟢 +2.2% 324,221 ops/sec [321,903..324,886] → 359,438 ops/sec [359,043..359,609] 🟢 +10.9%
Number.isFinite 300,520 ops/sec [299,354..302,148] → 317,544 ops/sec [313,439..319,207] 🟢 +5.7% 307,551 ops/sec [300,668..310,357] → 339,557 ops/sec [338,091..340,291] 🟢 +10.4%
Number.isInteger 302,444 ops/sec [301,303..303,613] → 324,376 ops/sec [320,090..326,972] 🟢 +7.3% 325,440 ops/sec [324,028..326,116] → 371,953 ops/sec [371,232..372,376] 🟢 +14.3%
Number.parseInt and parseFloat 245,477 ops/sec [243,416..246,288] → 255,332 ops/sec [247,624..259,464] 🟢 +4.0% 255,098 ops/sec [251,257..259,322] → 284,939 ops/sec [284,817..285,513] 🟢 +11.7%
objects.js — Interp: 🔴 5, 2 unch. · avg -2.3% · Bytecode: 🟢 4, 🔴 2, 1 unch. · avg +1.8%
Benchmark Interpreted Δ Bytecode Δ
create simple object 476,104 ops/sec [473,554..477,266] → 466,426 ops/sec [461,795..471,073] 🔴 -2.0% 582,201 ops/sec [574,475..591,546] → 569,220 ops/sec [563,978..571,681] 🔴 -2.2%
create nested object 228,944 ops/sec [227,266..229,970] → 225,397 ops/sec [223,582..226,329] 🔴 -1.5% 256,273 ops/sec [253,211..258,689] → 251,154 ops/sec [250,129..251,541] 🔴 -2.0%
create 50 objects via Array.from 9,211 ops/sec [9,187..9,232] → 9,002 ops/sec [8,949..9,083] 🔴 -2.3% 9,650 ops/sec [9,591..9,710] → 10,018 ops/sec [10,008..10,029] 🟢 +3.8%
property read 539,839 ops/sec [535,594..544,555] → 530,325 ops/sec [524,787..547,700] ~ overlap (-1.8%) 423,084 ops/sec [422,525..423,645] → 434,091 ops/sec [429,191..435,430] 🟢 +2.6%
Object.keys 274,265 ops/sec [272,240..275,102] → 271,448 ops/sec [263,552..275,226] ~ overlap (-1.0%) 297,884 ops/sec [294,080..303,999] → 323,444 ops/sec [321,786..324,106] 🟢 +8.6%
Object.entries 103,554 ops/sec [102,948..104,066] → 98,332 ops/sec [97,283..99,120] 🔴 -5.0% 112,248 ops/sec [110,801..113,592] → 111,906 ops/sec [111,051..112,660] ~ overlap (-0.3%)
spread operator 176,952 ops/sec [175,054..177,470] → 172,258 ops/sec [170,232..173,252] 🔴 -2.7% 204,443 ops/sec [203,790..205,571] → 208,415 ops/sec [206,884..209,389] 🟢 +1.9%
promises.js — Interp: 🔴 9, 3 unch. · avg -2.2% · Bytecode: 🟢 12 · avg +6.3%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 525,261 ops/sec [521,976..529,602] → 523,699 ops/sec [511,870..529,866] ~ overlap (-0.3%) 545,085 ops/sec [540,046..547,364] → 567,837 ops/sec [563,504..568,988] 🟢 +4.2%
new Promise(resolve => resolve(value)) 188,440 ops/sec [188,293..189,263] → 185,441 ops/sec [184,953..186,048] 🔴 -1.6% 221,144 ops/sec [218,812..221,859] → 238,444 ops/sec [237,312..238,685] 🟢 +7.8%
Promise.reject(reason) 539,148 ops/sec [534,496..542,613] → 525,208 ops/sec [521,266..528,912] 🔴 -2.6% 537,847 ops/sec [530,756..544,654] → 565,119 ops/sec [559,083..567,602] 🟢 +5.1%
resolve + then (1 handler) 174,756 ops/sec [174,158..175,392] → 167,318 ops/sec [165,369..169,209] 🔴 -4.3% 192,874 ops/sec [191,676..193,270] → 212,621 ops/sec [212,037..213,080] 🟢 +10.2%
resolve + then chain (3 deep) 67,129 ops/sec [65,796..67,778] → 67,782 ops/sec [66,983..67,991] ~ overlap (+1.0%) 74,661 ops/sec [74,202..76,338] → 81,373 ops/sec [81,267..81,799] 🟢 +9.0%
resolve + then chain (10 deep) 21,418 ops/sec [19,255..21,894] → 21,907 ops/sec [21,623..22,056] ~ overlap (+2.3%) 24,040 ops/sec [23,700..24,516] → 26,020 ops/sec [25,956..26,147] 🟢 +8.2%
reject + catch + then 99,088 ops/sec [98,771..99,787] → 95,654 ops/sec [95,522..95,977] 🔴 -3.5% 106,787 ops/sec [106,632..106,904] → 115,981 ops/sec [115,140..116,747] 🟢 +8.6%
resolve + finally + then 83,961 ops/sec [82,091..84,128] → 81,039 ops/sec [80,400..81,471] 🔴 -3.5% 90,706 ops/sec [89,378..91,106] → 97,201 ops/sec [96,998..97,325] 🟢 +7.2%
Promise.all (5 resolved) 30,802 ops/sec [30,418..31,418] → 29,850 ops/sec [29,586..30,051] 🔴 -3.1% 32,985 ops/sec [32,903..33,388] → 34,163 ops/sec [34,106..34,238] 🟢 +3.6%
Promise.race (5 resolved) 33,161 ops/sec [32,475..33,257] → 31,979 ops/sec [31,794..32,054] 🔴 -3.6% 35,375 ops/sec [35,077..35,524] → 36,446 ops/sec [36,187..36,670] 🟢 +3.0%
Promise.allSettled (5 mixed) 26,163 ops/sec [25,972..26,381] → 25,366 ops/sec [25,225..25,388] 🔴 -3.0% 27,907 ops/sec [27,803..27,996] → 29,248 ops/sec [29,032..29,360] 🟢 +4.8%
Promise.any (5 mixed) 31,162 ops/sec [30,749..31,529] → 29,978 ops/sec [29,832..30,213] 🔴 -3.8% 33,151 ops/sec [32,750..33,425] → 34,427 ops/sec [34,319..34,537] 🟢 +3.8%
regexp.js — Interp: 🟢 8, 3 unch. · avg +2.4% · Bytecode: 🟢 8, 3 unch. · avg +5.4%
Benchmark Interpreted Δ Bytecode Δ
regex literal creation 176,200 ops/sec [174,890..179,604] → 178,248 ops/sec [175,953..181,108] ~ overlap (+1.2%) 159,387 ops/sec [158,831..161,837] → 164,553 ops/sec [164,194..166,733] 🟢 +3.2%
new RegExp(pattern, flags) 148,864 ops/sec [148,496..150,167] → 153,377 ops/sec [152,364..154,292] 🟢 +3.0% 162,682 ops/sec [160,027..163,971] → 164,245 ops/sec [163,047..165,340] ~ overlap (+1.0%)
RegExp(existingRegex) returns the same regex 696,548 ops/sec [693,005..697,808] → 735,059 ops/sec [732,860..744,321] 🟢 +5.5% 936,540 ops/sec [935,422..938,336] → 1,044,602 ops/sec [1,044,095..1,045,651] 🟢 +11.5%
test() on a global regex 149,209 ops/sec [147,946..150,311] → 154,343 ops/sec [152,036..155,175] 🟢 +3.4% 178,622 ops/sec [178,417..179,346] → 177,599 ops/sec [176,282..178,705] ~ overlap (-0.6%)
exec() with capture groups 132,126 ops/sec [131,382..132,550] → 135,850 ops/sec [133,976..136,311] 🟢 +2.8% 155,909 ops/sec [154,977..158,057] → 161,822 ops/sec [160,560..162,408] 🟢 +3.8%
toString() 462,937 ops/sec [460,518..465,833] → 482,116 ops/sec [478,250..485,388] 🟢 +4.1% 605,763 ops/sec [604,702..608,942] → 667,192 ops/sec [663,922..668,034] 🟢 +10.1%
match() with global regex 43,354 ops/sec [43,125..43,792] → 43,999 ops/sec [43,677..44,166] ~ overlap (+1.5%) 43,487 ops/sec [43,244..44,538] → 46,831 ops/sec [46,621..47,265] 🟢 +7.7%
matchAll() with capture groups 22,326 ops/sec [22,178..22,416] → 22,605 ops/sec [22,573..22,674] 🟢 +1.3% 27,945 ops/sec [27,835..28,161] → 28,884 ops/sec [28,453..28,961] 🟢 +3.4%
replace() with global regex 41,272 ops/sec [40,681..41,486] → 42,106 ops/sec [42,032..42,139] 🟢 +2.0% 40,717 ops/sec [40,464..41,657] → 44,686 ops/sec [44,408..44,979] 🟢 +9.7%
search() with regex 88,601 ops/sec [87,782..88,791] → 90,529 ops/sec [89,875..90,932] 🟢 +2.2% 85,301 ops/sec [83,400..86,168] → 92,087 ops/sec [91,702..92,323] 🟢 +8.0%
split() with regex separator 44,154 ops/sec [43,545..44,259] → 44,081 ops/sec [43,855..44,237] ~ overlap (-0.2%) 43,962 ops/sec [43,526..44,743] → 44,768 ops/sec [44,133..45,009] ~ overlap (+1.8%)
strings.js — Interp: 🟢 4, 🔴 1, 6 unch. · avg +1.9% · Bytecode: 🟢 10, 🔴 1 · avg +7.0%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 421,482 ops/sec [418,068..425,475] → 400,797 ops/sec [399,821..401,231] 🔴 -4.9% 313,518 ops/sec [311,495..316,537] → 296,022 ops/sec [294,130..297,772] 🔴 -5.6%
template literal 623,950 ops/sec [621,605..625,164] → 732,153 ops/sec [695,292..736,955] 🟢 +17.3% 621,790 ops/sec [620,816..627,163] → 664,033 ops/sec [661,702..665,206] 🟢 +6.8%
string repeat 386,275 ops/sec [381,835..387,313] → 395,747 ops/sec [390,090..399,274] 🟢 +2.5% 432,924 ops/sec [428,639..434,619] → 490,084 ops/sec [488,945..490,771] 🟢 +13.2%
split and join 133,628 ops/sec [131,801..134,418] → 133,266 ops/sec [132,954..134,483] ~ overlap (-0.3%) 142,603 ops/sec [141,283..144,115] → 154,559 ops/sec [154,160..156,664] 🟢 +8.4%
indexOf and includes 163,758 ops/sec [161,069..164,113] → 161,031 ops/sec [158,284..162,436] ~ overlap (-1.7%) 178,751 ops/sec [178,104..179,036] → 199,275 ops/sec [198,030..201,017] 🟢 +11.5%
toUpperCase and toLowerCase 246,531 ops/sec [245,030..247,809] → 251,162 ops/sec [250,669..252,849] 🟢 +1.9% 305,857 ops/sec [303,974..309,516] → 328,048 ops/sec [326,222..328,580] 🟢 +7.3%
slice and substring 151,448 ops/sec [150,302..152,251] → 151,760 ops/sec [150,820..152,810] ~ overlap (+0.2%) 188,418 ops/sec [186,667..188,760] → 197,368 ops/sec [194,957..198,434] 🟢 +4.8%
trim operations 183,166 ops/sec [181,770..184,516] → 186,270 ops/sec [184,469..187,297] ~ overlap (+1.7%) 230,057 ops/sec [227,056..231,613] → 245,800 ops/sec [244,381..246,864] 🟢 +6.8%
replace and replaceAll 198,700 ops/sec [197,175..199,387] → 198,539 ops/sec [198,047..198,673] ~ overlap (-0.1%) 219,058 ops/sec [218,500..220,518] → 236,809 ops/sec [234,948..236,891] 🟢 +8.1%
startsWith and endsWith 131,662 ops/sec [130,891..132,874] → 133,895 ops/sec [132,461..134,783] ~ overlap (+1.7%) 148,531 ops/sec [147,719..148,621] → 162,846 ops/sec [162,178..162,919] 🟢 +9.6%
padStart and padEnd 188,221 ops/sec [186,444..190,119] → 193,826 ops/sec [192,579..195,103] 🟢 +3.0% 222,733 ops/sec [219,284..226,570] → 237,451 ops/sec [236,526..238,470] 🟢 +6.6%
typed-arrays.js — Interp: 🟢 7, 🔴 2, 13 unch. · avg +0.1% · Bytecode: 🟢 21, 1 unch. · avg +12.9%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 328,853 ops/sec [326,000..331,261] → 329,728 ops/sec [325,057..331,130] ~ overlap (+0.3%) 352,935 ops/sec [350,235..354,939] → 383,209 ops/sec [382,046..389,778] 🟢 +8.6%
new Int32Array(100) 295,819 ops/sec [293,598..298,293] → 291,459 ops/sec [288,475..294,101] ~ overlap (-1.5%) 318,837 ops/sec [316,724..321,344] → 352,872 ops/sec [351,527..354,937] 🟢 +10.7%
new Int32Array(1000) 182,324 ops/sec [181,262..182,921] → 179,655 ops/sec [177,204..181,301] ~ overlap (-1.5%) 186,307 ops/sec [185,462..189,090] → 217,614 ops/sec [216,989..219,939] 🟢 +16.8%
new Float64Array(100) 275,672 ops/sec [273,681..277,876] → 273,581 ops/sec [268,999..274,786] ~ overlap (-0.8%) 289,636 ops/sec [286,513..291,320] → 320,901 ops/sec [319,012..321,954] 🟢 +10.8%
Int32Array.from([...]) 172,472 ops/sec [170,739..172,950] → 170,027 ops/sec [168,868..171,272] ~ overlap (-1.4%) 172,795 ops/sec [172,002..173,876] → 208,327 ops/sec [207,735..209,043] 🟢 +20.6%
Int32Array.of(1, 2, 3, 4, 5) 299,245 ops/sec [293,434..299,837] → 285,885 ops/sec [285,688..286,469] 🔴 -4.5% 345,917 ops/sec [342,344..348,454] → 351,632 ops/sec [349,711..352,448] 🟢 +1.7%
sequential write 100 elements 3,519 ops/sec [3,505..3,537] → 3,478 ops/sec [3,466..3,525] ~ overlap (-1.2%) 15,414 ops/sec [15,270..15,518] → 17,092 ops/sec [16,959..17,154] 🟢 +10.9%
sequential read 100 elements 3,628 ops/sec [3,585..3,659] → 3,521 ops/sec [3,476..3,550] 🔴 -3.0% 10,374 ops/sec [10,337..10,450] → 11,402 ops/sec [11,317..11,433] 🟢 +9.9%
Float64Array write 100 elements 3,302 ops/sec [3,276..3,321] → 3,257 ops/sec [3,240..3,300] ~ overlap (-1.4%) 10,624 ops/sec [10,139..10,725] → 12,741 ops/sec [12,707..12,802] 🟢 +19.9%
fill(42) 44,115 ops/sec [43,958..44,183] → 44,195 ops/sec [44,155..44,383] ~ overlap (+0.2%) 44,259 ops/sec [44,149..44,340] → 57,105 ops/sec [56,927..57,119] 🟢 +29.0%
slice() 198,165 ops/sec [196,969..199,316] → 199,703 ops/sec [199,264..200,726] ~ overlap (+0.8%) 215,059 ops/sec [214,169..217,326] → 244,281 ops/sec [243,954..244,893] 🟢 +13.6%
map(x => x * 2) 7,757 ops/sec [7,673..7,827] → 7,711 ops/sec [7,616..7,756] ~ overlap (-0.6%) 8,272 ops/sec [8,173..8,354] → 9,314 ops/sec [9,286..9,344] 🟢 +12.6%
filter(x => x > 50) 7,995 ops/sec [7,903..8,115] → 7,971 ops/sec [7,899..8,066] ~ overlap (-0.3%) 9,136 ops/sec [9,099..9,150] → 10,453 ops/sec [10,435..10,469] 🟢 +14.4%
reduce (sum) 7,779 ops/sec [7,735..7,880] → 7,850 ops/sec [7,821..7,884] ~ overlap (+0.9%) 7,112 ops/sec [7,035..7,200] → 7,855 ops/sec [7,804..7,919] 🟢 +10.5%
sort() 171,295 ops/sec [170,432..171,820] → 174,341 ops/sec [173,771..174,572] 🟢 +1.8% 169,970 ops/sec [169,657..170,083] → 208,658 ops/sec [208,538..208,737] 🟢 +22.8%
indexOf() 410,421 ops/sec [407,100..411,835] → 420,161 ops/sec [419,464..422,061] 🟢 +2.4% 474,387 ops/sec [473,869..475,777] → 545,513 ops/sec [543,042..546,731] 🟢 +15.0%
reverse() 316,887 ops/sec [315,708..317,592] → 323,542 ops/sec [321,211..327,288] 🟢 +2.1% 346,217 ops/sec [345,800..346,654] → 388,199 ops/sec [385,679..389,210] 🟢 +12.1%
create view over existing buffer 386,556 ops/sec [382,798..389,904] → 393,817 ops/sec [391,886..394,940] 🟢 +1.9% 420,061 ops/sec [417,509..423,503] → 467,181 ops/sec [466,905..467,905] 🟢 +11.2%
subarray() 414,258 ops/sec [358,953..415,414] → 419,875 ops/sec [415,811..424,250] 🟢 +1.4% 491,875 ops/sec [490,159..492,544] → 530,223 ops/sec [527,686..531,066] 🟢 +7.8%
set() from array 531,625 ops/sec [531,020..535,544] → 553,178 ops/sec [550,407..559,118] 🟢 +4.1% 612,563 ops/sec [612,101..613,063] → 689,391 ops/sec [689,151..690,148] 🟢 +12.5%
for-of loop 4,992 ops/sec [4,916..5,047] → 5,062 ops/sec [5,034..5,075] ~ overlap (+1.4%) 17,703 ops/sec [17,582..17,795] → 19,613 ops/sec [19,510..19,710] 🟢 +10.8%
spread into array 16,836 ops/sec [16,729..16,940] → 17,135 ops/sec [16,998..17,218] 🟢 +1.8% 94,128 ops/sec [91,884..96,405] → 96,108 ops/sec [95,407..96,953] ~ overlap (+2.1%)

Measured on ubuntu-latest x64. Benchmark ranges compare cached main-branch min/max ops/sec with the PR run; overlapping ranges are treated as unchanged noise. Percentage deltas are secondary context.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
units/Goccia.Builtins.TestAssertions.Test.pas (3)

2315-2348: Consider using as cast for clearer test failure messages.

The direct cast TGocciaNativeFunctionValue(OnlyValue) at line 2339 will raise an obscure access violation if OnlyValue is nil or not a TGocciaNativeFunctionValue. Using an as cast would produce a clearer Invalid typecast exception pointing to the exact issue.

💡 Suggested improvement
-    TGocciaNativeFunctionValue(OnlyValue).Call(TestArgs, nil);
+    (OnlyValue as TGocciaNativeFunctionValue).Call(TestArgs, nil);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.TestAssertions.Test.pas` around lines 2315 - 2348, In
TestItOnlySkipsNonFocusedTests replace the raw Delphi typecast of OnlyValue
(TGocciaNativeFunctionValue(OnlyValue).Call(...)) with an "as" cast to produce a
clear InvalidCast exception when the value is nil or of the wrong type; locate
where OnlyValue is obtained via FScope.ResolveIdentifier('it') and change the
call site to use OnlyValue as TGocciaNativeFunctionValue before invoking Call
(or perform an explicit as-cast assignment to a TGocciaNativeFunctionValue
local) so failures report a descriptive typecast error instead of an obscure
access violation.

2418-2457: Test logic is correct; consider using as cast for clearer failure diagnostics.

The test correctly verifies that test.each expands tests for each table row, with arguments passed to the callback. The direct cast at line 2438 could use as for more descriptive errors if the type is wrong.

💡 Suggested improvement
-    ReturnedFunc := TGocciaFunctionBase(FAssertions.TestEach(EachArgs, nil));
+    ReturnedFunc := FAssertions.TestEach(EachArgs, nil) as TGocciaFunctionBase;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.TestAssertions.Test.pas` around lines 2418 - 2457,
Replace the hard C-style cast of the TestEach return with an "as" cast to get
clearer failure diagnostics: instead of coercing FAssertions.TestEach(...)
directly into TGocciaFunctionBase, use the "as" operator when assigning to
ReturnedFunc so a wrong type raises a descriptive exception; update the
assignment involving ReturnedFunc, FAssertions.TestEach and TGocciaFunctionBase
accordingly.

2476-2515: Test correctly verifies suite expansion; same as cast suggestion applies.

The test validates the two-pass registration/execution model: suite callbacks run during registration (adding suite:one, suite:two), then test bodies execute (adding test, test), giving 4 total events. The assertion at lines 2512-2514 properly verifies registration order.

The cast at line 2495 has the same minor improvement opportunity as noted for line 2438.

💡 Suggested improvement
-    ReturnedFunc := TGocciaFunctionBase(FAssertions.DescribeEach(EachArgs, nil));
+    ReturnedFunc := FAssertions.DescribeEach(EachArgs, nil) as TGocciaFunctionBase;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.TestAssertions.Test.pas` around lines 2476 - 2515,
Replace the direct typecast of the DescribeEach return with an "as" cast to
improve RTTI safety: in TestDescribeEachRegistersExpandedSuites change the
assignment to ReturnedFunc so it uses FAssertions.DescribeEach(EachArgs, nil) as
TGocciaFunctionBase instead of TGocciaFunctionBase(...); this mirrors the same
improvement suggested for the earlier cast and keeps the symbol names
ReturnedFunc and DescribeEach intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@units/Goccia.Builtins.TestAssertions.Test.pas`:
- Around line 2315-2348: In TestItOnlySkipsNonFocusedTests replace the raw
Delphi typecast of OnlyValue (TGocciaNativeFunctionValue(OnlyValue).Call(...))
with an "as" cast to produce a clear InvalidCast exception when the value is nil
or of the wrong type; locate where OnlyValue is obtained via
FScope.ResolveIdentifier('it') and change the call site to use OnlyValue as
TGocciaNativeFunctionValue before invoking Call (or perform an explicit as-cast
assignment to a TGocciaNativeFunctionValue local) so failures report a
descriptive typecast error instead of an obscure access violation.
- Around line 2418-2457: Replace the hard C-style cast of the TestEach return
with an "as" cast to get clearer failure diagnostics: instead of coercing
FAssertions.TestEach(...) directly into TGocciaFunctionBase, use the "as"
operator when assigning to ReturnedFunc so a wrong type raises a descriptive
exception; update the assignment involving ReturnedFunc, FAssertions.TestEach
and TGocciaFunctionBase accordingly.
- Around line 2476-2515: Replace the direct typecast of the DescribeEach return
with an "as" cast to improve RTTI safety: in
TestDescribeEachRegistersExpandedSuites change the assignment to ReturnedFunc so
it uses FAssertions.DescribeEach(EachArgs, nil) as TGocciaFunctionBase instead
of TGocciaFunctionBase(...); this mirrors the same improvement suggested for the
earlier cast and keeps the symbol names ReturnedFunc and DescribeEach intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5fd2d3f3-314f-492a-8517-ffe1cd41e3fc

📥 Commits

Reviewing files that changed from the base of the PR and between cf1c741 and f903cca.

📒 Files selected for processing (1)
  • units/Goccia.Builtins.TestAssertions.Test.pas

- Cast focused and each-returned callbacks to their concrete function types
- Simplify test assertions around `it.only`, `TestEach`, and `DescribeEach`
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
units/Goccia.Builtins.TestAssertions.Test.pas (1)

1573-1592: Use this helper to replace the stale { silent: true } setup.

Lines 1577-1579 now match the real RunTests contract, but the older skip/runIf cases below still hand-roll { silent: true }, which RunTests does not read. Reusing RunRegisteredTests there would remove dead setup and keep the suite aligned with the actual options surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.TestAssertions.Test.pas` around lines 1573 - 1592, The
tests still create a hand-rolled `{ silent: true }` arguments object instead of
using the new helper; replace those manual setups in the skip/runIf test cases
with a call to the existing RunRegisteredTests helper (which uses
CreateRunOptions and calls FAssertions.RunTests) so the tests use the real
options surface — locate occurrences that build a TGocciaArgumentsCollection
with a Params.AssignProperty('silent', ...) and swap them to call
RunRegisteredTests (adjusting expected return types to TGocciaObjectValue where
needed).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@units/Goccia.Builtins.TestAssertions.Test.pas`:
- Around line 1647-1766: These test helpers are calling TGocciaTestAssertions
internals directly (e.g., FAssertions.BeforeAll, FAssertions.Test,
FAssertions.AfterAll) — change RegisterBeforeAllSuiteCallback,
RegisterAfterAllSuiteCallback, RegisterFocusedSuiteCallback,
RegisterNonFocusedSuiteCallback and RegisterDescribeEachSuiteCallback to resolve
the public callable from the runtime scope (like the pattern used in
TestItOnlySkipsNonFocusedTests: use FScope to get the global/test/describe
callable) and invoke that resolved TGocciaValue/TGocciaNativeFunctionValue
instead of calling FAssertions.* directly so the tests exercise the actual
public API surface users call.

---

Nitpick comments:
In `@units/Goccia.Builtins.TestAssertions.Test.pas`:
- Around line 1573-1592: The tests still create a hand-rolled `{ silent: true }`
arguments object instead of using the new helper; replace those manual setups in
the skip/runIf test cases with a call to the existing RunRegisteredTests helper
(which uses CreateRunOptions and calls FAssertions.RunTests) so the tests use
the real options surface — locate occurrences that build a
TGocciaArgumentsCollection with a Params.AssignProperty('silent', ...) and swap
them to call RunRegisteredTests (adjusting expected return types to
TGocciaObjectValue where needed).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bff45268-5de0-4535-9e6c-8ffb28127645

📥 Commits

Reviewing files that changed from the base of the PR and between f903cca and 1ca80cb.

📒 Files selected for processing (1)
  • units/Goccia.Builtins.TestAssertions.Test.pas

Comment thread units/Goccia.Builtins.TestAssertions.Test.pas
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
units/Goccia.Builtins.TestAssertions.Test.pas (1)

1575-1585: Harden callable resolution failures for clearer diagnostics.

as TGocciaFunctionBase here can fail with generic cast/access violations, which makes broken API-surface wiring harder to diagnose in test failures. Consider explicit type checks with targeted exception messages.

Suggested refactor
 function TTestSkipAndConditionalAPIs.ResolveGlobalCallable(
   const AName: string): TGocciaFunctionBase;
+var
+  Value: TGocciaValue;
 begin
-  Result := FScope.ResolveIdentifier(AName) as TGocciaFunctionBase;
+  Value := FScope.ResolveIdentifier(AName);
+  if not (Value is TGocciaFunctionBase) then
+    raise Exception.CreateFmt('Expected callable global "%s"', [AName]);
+  Result := TGocciaFunctionBase(Value);
 end;

 function TTestSkipAndConditionalAPIs.ResolveCallableProperty(const AName,
   AProperty: string): TGocciaFunctionBase;
+var
+  Value: TGocciaValue;
 begin
-  Result := ResolveGlobalCallable(AName).GetProperty(AProperty) as TGocciaFunctionBase;
+  Value := ResolveGlobalCallable(AName).GetProperty(AProperty);
+  if not (Value is TGocciaFunctionBase) then
+    raise Exception.CreateFmt('Expected callable property "%s.%s"', [AName, AProperty]);
+  Result := TGocciaFunctionBase(Value);
 end;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.TestAssertions.Test.pas` around lines 1575 - 1585,
ResolveGlobalCallable and ResolveCallableProperty perform unchecked casts that
can raise obscure access violations; instead verify the resolved objects and
raise clear exceptions. In ResolveGlobalCallable, call
FScope.ResolveIdentifier(AName), check for nil and confirm the returned object
is a TGocciaFunctionBase (using "is" or TryCast), and raise a descriptive
exception naming AName and the actual type when the check fails; in
ResolveCallableProperty, call ResolveGlobalCallable(AName) then call
GetProperty(AProperty) and similarly check for nil/type before casting, raising
a descriptive exception that includes both AName and AProperty if the property
is missing or not a TGocciaFunctionBase.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@units/Goccia.Builtins.TestAssertions.Test.pas`:
- Around line 1575-1585: ResolveGlobalCallable and ResolveCallableProperty
perform unchecked casts that can raise obscure access violations; instead verify
the resolved objects and raise clear exceptions. In ResolveGlobalCallable, call
FScope.ResolveIdentifier(AName), check for nil and confirm the returned object
is a TGocciaFunctionBase (using "is" or TryCast), and raise a descriptive
exception naming AName and the actual type when the check fails; in
ResolveCallableProperty, call ResolveGlobalCallable(AName) then call
GetProperty(AProperty) and similarly check for nil/type before casting, raising
a descriptive exception that includes both AName and AProperty if the property
is missing or not a TGocciaFunctionBase.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd77bfb5-e7f8-40cd-b0a1-7049314d2f8f

📥 Commits

Reviewing files that changed from the base of the PR and between 8464b41 and a9e5674.

📒 Files selected for processing (1)
  • units/Goccia.Builtins.TestAssertions.Test.pas

@frostney frostney merged commit f3f30ac into main Apr 4, 2026
9 checks passed
@frostney frostney deleted the feature/vitest-phase2-lifecycle-structure-118 branch April 4, 2026 23:57
@frostney frostney added the new feature New feature or request label Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vitest API Phase 2: Lifecycle and structure (beforeAll, afterAll, test.each, test.only, test.todo)

1 participant