Skip to content

Add Vitest-style matcher support for test assertions#164

Merged
frostney merged 3 commits into
mainfrom
feature/vitest-phase1-matchers-117
Apr 2, 2026
Merged

Add Vitest-style matcher support for test assertions#164
frostney merged 3 commits into
mainfrom
feature/vitest-phase1-matchers-117

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Apr 1, 2026

Summary

  • Added new assertion matchers for expect(): toStrictEqual, toContainEqual, toMatchObject, and toMatch.
  • Implemented partial deep equality support in the comparison helpers to power object subset matching.
  • Expanded Pascal unit coverage for success, failure, negation, and non-matching type cases.
  • Updated docs to describe the new matcher surface in built-ins and testing references.

Testing

  • Added/updated Pascal unit tests in units/Goccia.Builtins.TestAssertions.Test.pas.

Summary by CodeRabbit

  • New Features

    • Added four new assertion matchers: toStrictEqual (Vitest-compatible alias), toContainEqual, toMatch, and toMatchObject.
  • Documentation

    • Updated testing docs and matcher reference with examples, compatibility notes, and available matcher highlights.
  • Tests

    • Expanded unit test coverage to validate the new matchers, negation behavior, subset/cyclic cases, and edge conditions.

- Add `toStrictEqual`, `toContainEqual`, `toMatchObject`, and `toMatch`
- Implement partial deep equality for matcher comparisons
- Update docs and unit tests for the new assertion surface
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac82d41d-6b47-443c-8e61-85f702f2f44e

📥 Commits

Reviewing files that changed from the base of the PR and between d76ba16 and 11343f3.

📒 Files selected for processing (2)
  • units/Goccia.Builtins.TestAssertions.Test.pas
  • units/Goccia.Evaluator.Comparison.pas
🚧 Files skipped from review as they are similar to previous changes (1)
  • units/Goccia.Evaluator.Comparison.pas

📝 Walkthrough

Walkthrough

Adds four new test assertion matchers—toStrictEqual, toContainEqual, toMatch, and toMatchObject—with implementations on TGocciaExpectationValue, a new IsPartialDeepEqual helper in the evaluator, expanded unit tests covering edge cases, and documentation updates. No exported public API signature changes beyond matcher functions.

Changes

Cohort / File(s) Summary
Documentation
AGENTS.md, docs/built-ins.md, docs/testing.md
Documented new matchers (toStrictEqual, toContainEqual, toMatch, toMatchObject) and added Vitest-compatibility notes and examples in testing and built-ins docs.
Matcher implementation
units/Goccia.Builtins.TestAssertions.pas
Added native methods on TGocciaExpectationValue: ToContainEqual, ToStrictEqual, ToMatchObject, ToMatch — include arg validation, type checks, deep/partial equality calls, substring logic, negation handling, and assertion reporting.
Comparison helper
units/Goccia.Evaluator.Comparison.pas
Added exported IsPartialDeepEqual and refactored IsDeepEqual with cycle protection and compared-pair tracking; implements recursive object-subset comparison for partial matching.
Unit tests
units/Goccia.Builtins.TestAssertions.Test.pas
Added numerous private test procedures in TTestExpectationMatchers to cover success/failure cases, negation, cyclic structures, nested subsets, and type-mismatch behavior for the new matchers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Poem

🐰
Four matchers hopped into the glen,
Checking deep, and then again.
Subsets, strings, arrays found true —
A rabbit cheers: the tests all grew.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add Vitest-style matcher support for test assertions' accurately captures the main objective: introducing new matchers (toStrictEqual, toContainEqual, toMatchObject, toMatch) aligned with Vitest patterns for enhanced assertion capabilities.
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-phase1-matchers-117

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.

Actionable comments posted: 2

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

206-213: Prefer member-definition tables when extending built-in surfaces.

The new matcher property registrations continue the ad hoc DefineProperty pattern. Please consider migrating matcher registration to Goccia object-model member-definition tables when touching this surface further.

As per coding guidelines: “Prefer Goccia-specific member-definition tables (Goccia.ObjectModel) plus RegisterMemberDefinitions over ad hoc RegisterNativeMethod blocks when adding/refactoring prototype/static surfaces.”

🤖 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 206 - 213, The new
matcher registrations use ad hoc DefineProperty calls for ToContainEqual,
ToStrictEqual, ToMatchObject and ToMatch; convert these to the Goccia
object-model member-definition approach by creating a Goccia.ObjectModel
member-definition table that maps the matcher names to
TGocciaNativeFunctionValue wrappers and then call RegisterMemberDefinitions
(instead of calling DefineProperty directly) to attach them to the appropriate
prototype/static surface; ensure the property attributes (pfConfigurable,
pfWritable) are preserved in the member-definition entries and reference the
existing TGocciaNativeFunctionValue constructors (e.g., ToContainEqual,
ToStrictEqual, ToMatchObject, ToMatch) when building the table.
units/Goccia.Builtins.TestAssertions.Test.pas (3)

485-545: Consider adding negation test for toContainEqual.

The implementation tests success, failure, and type-error cases, which is good. However, other matchers in this file (e.g., toBeNull, toEqual) include negation tests (not.toContainEqual). Adding a negation test would improve parity with existing coverage patterns.

Example negation test
procedure TTestExpectationMatchers.TestToContainEqualNegated;
var
  A: TGocciaArgumentsCollection;
  ActualArray: TGocciaArrayValue;
  ExpectedObject: TGocciaObjectValue;
begin
  ActualArray := TGocciaArrayValue.Create;
  ActualArray.Elements.Add(TGocciaNumberLiteralValue.Create(1));

  ExpectedObject := TGocciaObjectValue.Create;
  ExpectedObject.AssignProperty('id', TGocciaNumberLiteralValue.Create(99));

  A := TGocciaArgumentsCollection.Create([ExpectedObject]);
  try
    ExpectPass(MakeExpectation(ActualArray, True).ToContainEqual(A, nil));
  finally
    A.Free;
  end;
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 485 - 545, Add a
negation unit test for toContainEqual to match existing matcher coverage: create
a new procedure TestToContainEqualNegated that builds an ActualArray (e.g., with
a number element), an ExpectedObject with a non-matching property, wrap
ExpectedObject in TGocciaArgumentsCollection, then call
ExpectPass(MakeExpectation(ActualArray, True).ToContainEqual(A, nil)); make sure
to free A and any created values and register the new test method alongside
other TTestExpectationMatchers tests (reference symbols:
TestToContainEqualArrayValue, TestToContainEqualArrayMissing,
TestToContainEqualOnNonArray, MakeExpectation, ToContainEqual,
TGocciaArgumentsCollection).

598-683: Consider adding negation test for toMatchObject.

Similar to the toContainEqual comment, the toMatchObject tests cover success, nested success, failure, and type-error cases well, but lack a negation test for not.toMatchObject. This would align with the test coverage pattern used for other matchers like toBeNull and toStrictEqual.

🤖 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 598 - 683, Add
negation coverage for ToMatchObject: create a new test (in
TTestExpectationMatchers) that exercises the negative form of ToMatchObject via
MakeExpectation(...).Not.ToMatchObject and asserts both outcomes—i) when Actual
and Expected match, expect the negated matcher to fail (use ExpectFail), and ii)
when Actual and Expected differ, expect the negated matcher to pass (use
ExpectPass). Reuse the same object construction patterns from TestToMatchObject,
TestToMatchObjectMismatch and TestToMatchObjectNestedSubset so the test targets
ToMatchObject negation behavior consistently.

685-721: Consider adding negation test for toMatch.

The toMatch tests cover the core cases effectively. A not.toMatch negation test would complete the coverage pattern consistent with other matchers in this file.

🤖 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 685 - 721, Add a
negation test method (e.g. TestToMatchNegation) that uses
TGocciaArgumentsCollection and TGocciaStringLiteralValue with
MakeExpectation(...).Not.ToMatch to assert negation behavior: create an args
collection with 'world' and Assert
ExpectFail(MakeExpectation(TGocciaStringLiteralValue.Create('hello
world')).Not.ToMatch(A, nil)) for the case where the substring exists, and
another case with a different arg (e.g. 'planet') asserting
ExpectPass(MakeExpectation(TGocciaStringLiteralValue.Create('hello
world')).Not.ToMatch(A, nil)); follow the same allocation/try/finally A.Free
pattern as other TestToMatch functions.
🤖 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.pas`:
- Around line 318-503: Add JavaScript end-to-end tests under tests/ that
exercise the four new matchers: toStrictEqual, toContainEqual, toMatchObject,
and toMatch; for each matcher include (1) happy-path assertions that pass, (2)
type-validation error cases (e.g., passing non-array to toContainEqual,
non-object to toMatchObject, non-string to toMatch) and assert the runner
reports the expected failure, and (3) negation cases using not.toStrictEqual /
not.toContainEqual / not.toMatchObject / not.toMatch to ensure negated logic is
correct; mirror the scenarios covered in the Pascal unit tests
(units/Goccia.Builtins.TestAssertions.Test.pas) and place each matcher’s tests
in clearly named files in tests/ so the e2e test runner will pick them up.

In `@units/Goccia.Evaluator.Comparison.pas`:
- Around line 375-385: The loop that checks subset matching uses
ActualObj.HasProperty which traverses the prototype chain and can yield false
positives; change the ownership check to verify the property is an own property
(e.g., replace the HasProperty call with a HasOwn/OwnProperty-style check or
inspect property descriptor ownership) before calling ActualObj.GetProperty, so
in the for-loop over ExpectedKeys we only consider keys that are actually owned
by the actual object when invoking
IsPartialDeepEqual(ActualObj.GetProperty(Key), ExpectedObj.GetProperty(Key)).

---

Nitpick comments:
In `@units/Goccia.Builtins.TestAssertions.pas`:
- Around line 206-213: The new matcher registrations use ad hoc DefineProperty
calls for ToContainEqual, ToStrictEqual, ToMatchObject and ToMatch; convert
these to the Goccia object-model member-definition approach by creating a
Goccia.ObjectModel member-definition table that maps the matcher names to
TGocciaNativeFunctionValue wrappers and then call RegisterMemberDefinitions
(instead of calling DefineProperty directly) to attach them to the appropriate
prototype/static surface; ensure the property attributes (pfConfigurable,
pfWritable) are preserved in the member-definition entries and reference the
existing TGocciaNativeFunctionValue constructors (e.g., ToContainEqual,
ToStrictEqual, ToMatchObject, ToMatch) when building the table.

In `@units/Goccia.Builtins.TestAssertions.Test.pas`:
- Around line 485-545: Add a negation unit test for toContainEqual to match
existing matcher coverage: create a new procedure TestToContainEqualNegated that
builds an ActualArray (e.g., with a number element), an ExpectedObject with a
non-matching property, wrap ExpectedObject in TGocciaArgumentsCollection, then
call ExpectPass(MakeExpectation(ActualArray, True).ToContainEqual(A, nil)); make
sure to free A and any created values and register the new test method alongside
other TTestExpectationMatchers tests (reference symbols:
TestToContainEqualArrayValue, TestToContainEqualArrayMissing,
TestToContainEqualOnNonArray, MakeExpectation, ToContainEqual,
TGocciaArgumentsCollection).
- Around line 598-683: Add negation coverage for ToMatchObject: create a new
test (in TTestExpectationMatchers) that exercises the negative form of
ToMatchObject via MakeExpectation(...).Not.ToMatchObject and asserts both
outcomes—i) when Actual and Expected match, expect the negated matcher to fail
(use ExpectFail), and ii) when Actual and Expected differ, expect the negated
matcher to pass (use ExpectPass). Reuse the same object construction patterns
from TestToMatchObject, TestToMatchObjectMismatch and
TestToMatchObjectNestedSubset so the test targets ToMatchObject negation
behavior consistently.
- Around line 685-721: Add a negation test method (e.g. TestToMatchNegation)
that uses TGocciaArgumentsCollection and TGocciaStringLiteralValue with
MakeExpectation(...).Not.ToMatch to assert negation behavior: create an args
collection with 'world' and Assert
ExpectFail(MakeExpectation(TGocciaStringLiteralValue.Create('hello
world')).Not.ToMatch(A, nil)) for the case where the substring exists, and
another case with a different arg (e.g. 'planet') asserting
ExpectPass(MakeExpectation(TGocciaStringLiteralValue.Create('hello
world')).Not.ToMatch(A, nil)); follow the same allocation/try/finally A.Free
pattern as other TestToMatch functions.
🪄 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: 09f0f184-259c-4179-8d49-18321d5be5fa

📥 Commits

Reviewing files that changed from the base of the PR and between d88aa4d and 45c4e2f.

📒 Files selected for processing (6)
  • AGENTS.md
  • docs/built-ins.md
  • docs/testing.md
  • units/Goccia.Builtins.TestAssertions.Test.pas
  • units/Goccia.Builtins.TestAssertions.pas
  • units/Goccia.Evaluator.Comparison.pas

Comment on lines +318 to +503
function TGocciaExpectationValue.ToContainEqual(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue;
var
Expected: TGocciaValue;
I: Integer;
Contains: Boolean;
begin
TGocciaArgumentValidator.RequireExactly(AArgs, 1, 'toContainEqual', FTestAssertions.ThrowError);

if not (FActualValue is TGocciaArrayValue) then
begin
if FIsNegated then
TGocciaTestAssertions(FTestAssertions).AssertionPassed('toContainEqual')
else
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toContainEqual',
'Expected an array but received ' + FActualValue.ToStringLiteral.Value);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
Exit;
end;

Expected := AArgs.GetElement(0);
Contains := False;
for I := 0 to TGocciaArrayValue(FActualValue).Elements.Count - 1 do
if IsDeepEqual(TGocciaArrayValue(FActualValue).Elements[I], Expected) then
begin
Contains := True;
Break;
end;

if FIsNegated then
Contains := not Contains;

if Contains then
begin
TGocciaTestAssertions(FTestAssertions).AssertionPassed('toContainEqual');
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
end
else
begin
if FIsNegated then
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toContainEqual',
'Expected ' + FActualValue.ToStringLiteral.Value + ' not to contain equal ' + Expected.ToStringLiteral.Value)
else
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toContainEqual',
'Expected ' + FActualValue.ToStringLiteral.Value + ' to contain equal ' + Expected.ToStringLiteral.Value);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
end;
end;

function TGocciaExpectationValue.ToStrictEqual(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue;
var
Expected: TGocciaValue;
IsEqual: Boolean;
begin
TGocciaArgumentValidator.RequireExactly(AArgs, 1, 'toStrictEqual', FTestAssertions.ThrowError);

Expected := AArgs.GetElement(0);
IsEqual := IsDeepEqual(FActualValue, Expected);

if FIsNegated then
IsEqual := not IsEqual;

if IsEqual then
begin
TGocciaTestAssertions(FTestAssertions).AssertionPassed('toStrictEqual');
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
end
else
begin
if FIsNegated then
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toStrictEqual',
'Expected ' + FActualValue.ToStringLiteral.Value + ' not to strictly equal ' + Expected.ToStringLiteral.Value)
else
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toStrictEqual',
'Expected ' + FActualValue.ToStringLiteral.Value + ' to strictly equal ' + Expected.ToStringLiteral.Value);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
end;
end;

function TGocciaExpectationValue.ToMatchObject(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue;
var
Expected: TGocciaValue;
Matches: Boolean;
begin
TGocciaArgumentValidator.RequireExactly(AArgs, 1, 'toMatchObject', FTestAssertions.ThrowError);

Expected := AArgs.GetElement(0);

if not (FActualValue is TGocciaObjectValue) then
begin
if FIsNegated then
TGocciaTestAssertions(FTestAssertions).AssertionPassed('toMatchObject')
else
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toMatchObject',
'Expected an object but received ' + FActualValue.ToStringLiteral.Value);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
Exit;
end;

if not (Expected is TGocciaObjectValue) then
begin
if FIsNegated then
TGocciaTestAssertions(FTestAssertions).AssertionPassed('toMatchObject')
else
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toMatchObject',
'Expected a match object but received ' + Expected.ToStringLiteral.Value);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
Exit;
end;

Matches := IsPartialDeepEqual(FActualValue, Expected);

if FIsNegated then
Matches := not Matches;

if Matches then
begin
TGocciaTestAssertions(FTestAssertions).AssertionPassed('toMatchObject');
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
end
else
begin
if FIsNegated then
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toMatchObject',
'Expected ' + FActualValue.ToStringLiteral.Value + ' not to match object ' + Expected.ToStringLiteral.Value)
else
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toMatchObject',
'Expected ' + FActualValue.ToStringLiteral.Value + ' to match object ' + Expected.ToStringLiteral.Value);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
end;
end;

function TGocciaExpectationValue.ToMatch(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue;
var
Expected: TGocciaValue;
ActualString: string;
ExpectedString: string;
Matches: Boolean;
begin
TGocciaArgumentValidator.RequireExactly(AArgs, 1, 'toMatch', FTestAssertions.ThrowError);

if not (FActualValue is TGocciaStringLiteralValue) then
begin
if FIsNegated then
TGocciaTestAssertions(FTestAssertions).AssertionPassed('toMatch')
else
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toMatch',
'Expected a string but received ' + FActualValue.ToStringLiteral.Value);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
Exit;
end;

Expected := AArgs.GetElement(0);
if not (Expected is TGocciaStringLiteralValue) then
begin
if FIsNegated then
TGocciaTestAssertions(FTestAssertions).AssertionPassed('toMatch')
else
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toMatch',
'Expected a string pattern but received ' + Expected.ToStringLiteral.Value);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
Exit;
end;

ActualString := FActualValue.ToStringLiteral.Value;
ExpectedString := Expected.ToStringLiteral.Value;
Matches := Pos(ExpectedString, ActualString) > 0;

if FIsNegated then
Matches := not Matches;

if Matches then
begin
TGocciaTestAssertions(FTestAssertions).AssertionPassed('toMatch');
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
end
else
begin
if FIsNegated then
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toMatch',
'Expected ' + FActualValue.ToStringLiteral.Value + ' not to match ' + Expected.ToStringLiteral.Value)
else
TGocciaTestAssertions(FTestAssertions).AssertionFailed('toMatch',
'Expected ' + FActualValue.ToStringLiteral.Value + ' to match ' + Expected.ToStringLiteral.Value);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
end;
end;
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 1, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Checking JS/TS test usage of newly added matchers..."
rg -n --iglob 'tests/**/*.{js,jsx,ts,tsx,mjs}' '\.(toStrictEqual|toContainEqual|toMatchObject|toMatch)\(' || true

echo
echo "Checking native Pascal test coverage entries..."
rg -n 'ToStrictEqual|ToContainEqual|ToMatchObject|ToMatch' units/Goccia.Builtins.TestAssertions.Test.pas || true

echo
echo "Expected: matcher usage appears in tests/**/* for JS e2e coverage, not only in units/*.Test.pas."

Repository: frostney/GocciaScript

Length of output: 3976


Add JavaScript end-to-end tests for the new matchers.

New matchers (toStrictEqual, toContainEqual, toMatchObject, toMatch) are covered by native Pascal unit tests in units/Goccia.Builtins.TestAssertions.Test.pas, but no JavaScript tests exist under tests/. JavaScript end-to-end tests are the primary testing layer for GocciaScript features. Add test cases under tests/ covering the same scenarios: happy paths (passing assertions), error cases (type validation), and negation (not.toXxx()).

🤖 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 318 - 503, Add
JavaScript end-to-end tests under tests/ that exercise the four new matchers:
toStrictEqual, toContainEqual, toMatchObject, and toMatch; for each matcher
include (1) happy-path assertions that pass, (2) type-validation error cases
(e.g., passing non-array to toContainEqual, non-object to toMatchObject,
non-string to toMatch) and assert the runner reports the expected failure, and
(3) negation cases using not.toStrictEqual / not.toContainEqual /
not.toMatchObject / not.toMatch to ensure negated logic is correct; mirror the
scenarios covered in the Pascal unit tests
(units/Goccia.Builtins.TestAssertions.Test.pas) and place each matcher’s tests
in clearly named files in tests/ so the e2e test runner will pick them up.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

JavaScript end-to-end tests are the wrong abstraction layer for the test assertions. This is not a language feature in itself and we can't use the implementation to test the implementation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@frostney, that's a completely valid point — testing the assertion matchers via JavaScript expect() calls would be circular, since the test runner itself depends on the very matchers being validated. The native Pascal unit tests in units/Goccia.Builtins.TestAssertions.Test.pas are clearly the right layer for this. I've noted this for future reviews.


✏️ Learnings added
Learnt from: frostney
Repo: frostney/GocciaScript PR: 164
File: units/Goccia.Builtins.TestAssertions.pas:318-503
Timestamp: 2026-04-02T04:42:34.022Z
Learning: In frostney/GocciaScript, the `TGocciaExpectationValue` matchers (e.g. `toBe`, `toEqual`, `toStrictEqual`, `toContainEqual`, `toMatchObject`, `toMatch`, and similar matchers in `units/Goccia.Builtins.TestAssertions.pas`) must NOT be tested via JavaScript end-to-end tests under `tests/`, because that would use the implementation to test itself (circular dependency). The correct and only appropriate testing layer for these matchers is the native Pascal unit tests in `units/Goccia.Builtins.TestAssertions.Test.pas`. Do not flag missing JS e2e test coverage for test-assertion matcher methods.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T22:00:59.812Z
Learning: Applies to units/**/*.pas : All values inherit from `TGocciaValue`. Prefer VMT methods `GetProperty(Name)`, `SetProperty(Name, Value)`, `IsPrimitive`, and `IsCallable` over type-checking at call sites. Do not use `is` type checks for fundamental type-system properties.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T22:00:59.812Z
Learning: Applies to units/**/*.Test.pas : In Pascal tests, use `Value.ToNumberLiteral.IsNaN` (not `Math.IsNaN`) for checking NaN values, as special values store `0.0` internally.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-01T19:11:21.599Z
Learning: JavaScript end-to-end tests are the primary way of testing GocciaScript. Add JavaScript tests under tests/ for any new feature or bug fix, keeping tests isolated and grouped by feature/filename, following existing directory structure

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T22:00:59.812Z
Learning: JavaScript end-to-end tests are the primary way of testing GocciaScript. Always verify changes by running: `./build.pas testrunner && ./build/TestRunner tests`

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T22:00:59.812Z
Learning: Applies to tests/**/*.{js,jsx,ts,tsx,mjs} : JavaScript tests should cover happy paths, edge cases, and error cases. Keep tests isolated and grouped by feature/filename.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T22:00:59.812Z
Learning: Applies to units/**/*.Test.pas : When modifying AST logic, scope chain, evaluator, or value types, update the native Pascal tests in `units/*.Test.pas` to reflect any changes in behaviour (e.g. new parameters, changed return semantics). Both JavaScript tests and native Pascal tests must pass.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-08T04:38:51.695Z
Learning: Applies to units/**/*.Test.pas : Native Pascal unit tests in `units/*.Test.pas` must be updated whenever AST logic, scope chain, evaluator, or value types are modified. Both JavaScript tests and native Pascal tests must pass.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-08T04:38:51.694Z
Learning: Always verify JavaScript test changes by running `./build.pas testrunner && ./build/TestRunner tests`. Both JavaScript tests and native Pascal tests must pass when modifying internal implementation details.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T22:00:59.812Z
Learning: Applies to **/*.{js,jsx,ts,tsx,mjs} : When implementing a new JavaScript language feature, create test files under the `tests/` directory following the existing directory structure (`tests/language/` for language features, `tests/built-ins/` for built-in objects). Each test file should focus on a single concern.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-01T19:11:21.599Z
Learning: When modifying AST logic, scope chain, evaluator, or value types, build and run the native Pascal test suite: ./build.pas clean tests && for t in build/Goccia.*.Test; do "$t"; done. Update native tests in units/*.Test.pas to reflect changes

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-01T19:11:21.599Z
Learning: Add/update tests when implementing a new language feature following existing patterns in tests/. For AST logic, scope chain, evaluator, or value type changes, also update units/*.Test.pas

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-01T19:11:21.592Z
Learning: Applies to tests/**/*.js : Edge case tests (NaN, Infinity, negative indices, clamping, empty collections, boundary conditions) belong in the same file as the happy-path tests for that method, not in separate edge-cases.js files

Learnt from: frostney
Repo: frostney/GocciaScript PR: 147
File: units/Goccia.VM.pas:3042-3084
Timestamp: 2026-03-31T18:38:20.149Z
Learning: Repo: frostney/GocciaScript — units/Goccia.VM.pas (PR `#147`)
As of commit 457e712: (1) TGocciaVM.GetPropertyValue performs own-only private static accessor lookup on '#…' for TGocciaClassValue and throws TypeError on class-brand misses; (2) TGocciaVM.SetPropertyValue avoids inherited private accessor resolution, but still allows raw private static writes via SetRawPrivateValue to support current class initialization that emits OP_SET_PROP_CONST for private static fields. Planned follow-up is to add dedicated opcodes to define private static fields at class-declaration time and then make SetPropertyValue strict (throw on brand miss with no fallback). Do not re-flag the current fallback until those opcodes exist.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-08T04:38:51.695Z
Learning: Applies to **/Goccia.Engine.pas : Built-ins are registered by the engine via `TGocciaGlobalBuiltins` flags in `DefaultGlobals`. The TestRunner adds `ggTestAssertions` for `describe`/`test`/`expect`. The BenchmarkRunner adds `ggBenchmark` for `bench()` with setup/teardown.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T22:00:59.812Z
Learning: Applies to units/**/*.pas : Built-ins are registered by the engine via `TGocciaGlobalBuiltins` flags. The TestRunner adds `ggTestAssertions` for the test framework. The BenchmarkRunner adds `ggBenchmark` for the benchmark framework.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 107
File: units/Goccia.Runtime.Operations.pas:211-212
Timestamp: 2026-03-23T12:37:30.936Z
Learning: Repo: frostney/GocciaScript — units/Goccia.Runtime.Operations.pas (PR `#107`)
Learning: In TGocciaRuntimeOperations.MarkExternalRoots, entries in FModuleCache (cached module namespace/JSON records) must be traversed and their TSouffleValue references marked as external roots to survive GC. This behavior was added in commit 36f69cc; do not re‑flag missing FModuleCache rooting unless the logic regresses.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 58
File: units/Goccia.Runtime.Operations.pas:5959-6132
Timestamp: 2026-03-10T11:17:46.617Z
Learning: GocciaScript’s Promise statics are intentionally not species/subclass-aware. Interpreter-level implementations (PromiseAll, PromiseRace, PromiseAllSettled, PromiseAny, withResolvers, try, resolve, reject) construct plain TGocciaPromiseValue via WrapAsPromise and do not use the call receiver (AThisValue). The Souffle VM bridge mirrors this by not forwarding AReceiver and by constructing TGocciaPromiseValue directly where applicable. Do not flag missing receiver-based construction for Promise statics. This applies to units/Goccia.Runtime.Operations.pas and related Promise implementations; ensure consistency across related Pas files in the frostney/GocciaScript repository.

Learnt from: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T22:00:59.812Z
Learning: Applies to units/**/*.pas : `type`/`interface` declarations and `import type`/`export type` must produce `TGocciaEmptyStatement` (no-op at runtime). Access modifiers (`public`, `protected`, `private`, `readonly`, `override`, `abstract`) in class bodies must be consumed and discarded.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 109
File: units/Goccia.Runtime.Operations.pas:2797-2828
Timestamp: 2026-03-23T15:57:21.755Z
Learning: Repo frostney/GocciaScript — PR `#109`: Native Map/Set scaffolding
- Current behavior: ToSouffleValue wraps TGocciaMapValue/TGocciaSetValue as TGocciaWrappedValue; TGocciaSouffleMap/TGocciaSouffleSet instances are not produced in normal execution.
- Pre-wired fast paths: GetProperty, GetIterator, and IteratorNext recognize TGocciaSouffleMap/TGocciaSouffleSet and their iterator types; these are staged for future activation.
- Deferred work: When Construct is updated to produce native collection types, add corresponding fast paths in HasProperty, SetProperty, and DeleteProperty.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 87
File: units/Goccia.Runtime.Operations.pas:3383-3387
Timestamp: 2026-03-17T16:28:05.526Z
Learning: Repo: frostney/GocciaScript — Follow-up tracking
Learning: Splitting the blueprint bridge caches (value bridge vs construct bridge) is tracked in issue `#99`. Until that lands, sharing FBlueprintBridgeCache between UnwrapToGocciaValue (BlueprintToClassValue) and Construct (ConvertBlueprintToClassValue) should not block PRs; reference `#99` instead of re-flagging.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 100
File: tests/language/classes/blueprint-bridge-consistency.js:21-101
Timestamp: 2026-03-17T16:57:51.305Z
Learning: Repo: frostney/GocciaScript — tests/language/classes/
The `prototype.constructor` assertion for blueprint-bridged classes in bytecode mode is a pre-existing issue tracked in `#101`. Do not re-flag missing `prototype.constructor` checks in bridge-related tests as a new concern; reference `#101` instead.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 139
File: units/Goccia.ScriptLoader.JSON.Test.pas:1-113
Timestamp: 2026-03-30T13:02:29.132Z
Learning: In frostney/GocciaScript, CI workflows (`.github/workflows/ci.yml` and `.github/workflows/pr.yml`) use glob patterns (`build/Goccia.*.Test*` for artifact upload, `Goccia.*.Test` / `Goccia.*.Test.exe` for execution) to discover and run native Pascal test binaries automatically. Do not flag missing entries in a hard-coded test list — new `units/*.Test.pas` test programs are picked up automatically via these globs as of commit b63911d (PR `#139`).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 53
File: souffle/Souffle.Wasm.Emitter.pas:1467-1477
Timestamp: 2026-03-09T17:21:38.193Z
Learning: In this codebase (Frostney/GocciaScript), for Pascal/Delphi (FPC) files using AnsiString with {$mode delphi} {H+}, treat String as byte-indexed. Length(str) returns byte count and Byte(str[I]) is a no-op cast. Do not replace per-byte copy loops like Data[I-1] := Byte(AStr[I]) with TEncoding.GetBytes, since TEncoding is a Delphi RTL feature unavailable in FPC's SysUtils and would break the build. Apply this guidance to all .pas files under the project; ensure manual per-byte copying logic remains correct for UTF-8 content encoded in AnsiString, and avoid introducing RTL dependencies not supported by FPC.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 63
File: units/Goccia.Runtime.Operations.pas:0-0
Timestamp: 2026-03-11T01:50:11.071Z
Learning: Replace hardcoded string literals 'now', 'toJSON', and 'timeOrigin' with the new constants from Goccia.Constants.PropertyNames (PROP_NOW, PROP_TO_JSON, PROP_TIME_ORIGIN). Apply this refactor across the repository wherever these literals are used, including but not limited to units/Goccia.Runtime.Operations.pas and units/Goccia.Builtins.Performance.pas. Use a project-wide search (e.g., regex or code navigation) to identify all occurrences, update to the respective constants, and adjust imports or uses accordingly. Ensure all references compile and behavior remains unchanged.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 63
File: units/Goccia.Runtime.Operations.pas:7514-7544
Timestamp: 2026-03-11T08:22:20.482Z
Learning: When bridging a native namespace object to a TSouffleRecord (e.g., performance) in units/Goccia.Runtime.Operations.pas, always set Result.Delegate := ARuntime.VM.RecordDelegate after TSouffleRecord.Create(...). This ensures Object.prototype methods (toString/hasOwnProperty/valueOf/toLocaleString) work in bytecode mode and NativeRecordToString honors Symbol.toStringTag. Apply this pattern to BuildPerformanceRecord and any future bridged records.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/HashMap.pas:266-309
Timestamp: 2026-03-17T22:17:37.410Z
Learning: Do not flag enumeration in THashMap<TKey, TValue> as concurrent-modification unsafe. The no-mutation contract is maintained during enumeration: GetEnumerator shares the internal FSlots array (no per-call allocation) and only mutation occurs via destructor or Clear, not during active iteration. This pattern should be recognized across similar Pascal units/pascal collection implementations; when reviewing code, ensure no concurrent-modification warnings are raised for shared internal structures during enumeration if the no-mutation contract is preserved.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/OrderedMap.pas:0-0
Timestamp: 2026-03-17T22:17:55.588Z
Learning: Enforce the constraint on TOrderedMap<TKey, TValue> in units/OrderedMap.pas: TKey must be an unmanaged, fixed-size value type (no strings, dynamic arrays, or records with managed/pointer fields). HashKey should use fast paths for SizeOf(TKey)=8 (QWord) and SizeOf(TKey)=4 (Cardinal); KeysEqual should compare via direct PQWord/PCardinal for those sizes. The raw-byte DJB2/CompareMem fallback is dead code for current uses. Document this constraint in a unit-header comment as the enforcement mechanism. Do NOT re-flag the byte-wise hash/equality in future reviews as long as all TOrderedMap specializations use pointer-sized unmanaged keys.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/ScopeMap.pas:0-0
Timestamp: 2026-03-17T22:18:00.984Z
Learning: Do not re-introduce TScopeMap. In frostney/GocciaScript, TScopeMap<TValue> was removed due to performance regressions from its linear-scan Add-as-upsert behavior. Reviews should flag any use of TScopeMap in Units/ScopeMap.pas or elsewhere and ensure bindings use TOrderedStringMap<TLexicalBinding> instead. If reviewing similar Pascal files, prefer avoiding upsert-like add semantics that overwrite existing entries, as they correlated with bytecode performance drops. Ensure changes adhere to the established replacement: TOrderedStringMap<TLexicalBinding>.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/BaseMap.pas:22-22
Timestamp: 2026-03-18T08:12:24.165Z
Learning: Shared/utility units under the repository (e.g., units/BaseMap.pas, HashMap.pas, OrderedMap.pas, etc.) that are used across multiple parts of the project should not be required to use the Goccia.* prefix. A separate naming convention will be defined in a follow-up issue. Do not flag missing Goccia.* prefix for these shared infrastructure units in reviews.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/BaseMap.pas:22-22
Timestamp: 2026-03-18T08:12:24.165Z
Learning: Do not flag missing Goccia.* prefixes for shared/utility units that are used across multiple parts of the project or can be extracted as standalone infrastructure (e.g., BaseMap.pas, HashMap.pas, OrderedMap.pas, OrderedStringMap.pas, GC, test assertion units). A separate naming convention (to be defined in issue `#104`) should apply to these targets. When reviewing, apply this guideline to all similar shared utility units under the units directory; for one-off implementations outside this scope, continue with the existing naming expectations.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 110
File: units/Goccia.Runtime.Operations.pas:0-0
Timestamp: 2026-03-23T11:25:00.381Z
Learning: In this repo’s GocciaScript Pascal code, when implementing thenable detection/callability checks (e.g., around Await/unwrap logic), always read the thenable’s `then` property using `Goccia.Constants.PropertyNames.PROP_THEN` rather than a string literal. If the value being inspected is a `TSouffleRecord`, unwrap the retrieved `then` property via `UnwrapToGocciaValue` before testing whether it is callable (e.g., in `TGocciaRuntimeOperations.AwaitValue`).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 107
File: units/Goccia.Runtime.Operations.pas:4349-4353
Timestamp: 2026-03-23T12:38:54.412Z
Learning: In Pascal source under units/, when decoding JSON string escape sequences that represent Unicode code points and then converting them to UTF-8, prefer using the existing helper (implemented via CodePointToUTF8) that correctly combines UTF-16 surrogate pairs (\uD800–\uDBFF + \uDC00–\uDFFF) into a single Unicode code point before UTF-8 encoding. Avoid ad-hoc conversions like Char(StrToInt(...)) for code-point→UTF-8 conversion logic; use the helper instead to ensure surrogate pairs are handled correctly.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 109
File: units/Goccia.Runtime.Operations.pas:6337-6344
Timestamp: 2026-03-23T15:57:55.802Z
Learning: In Pascal unit files under units/, any iterator result record objects produced by native iterators (e.g., in routines analogous to NativeMapIteratorNext / NativeSetIteratorNext) must: (1) set `Rec.Delegate := FVM.RecordDelegate`; and (2) set properties using `PROP_VALUE` / `PROP_DONE` constants from `Goccia.Constants.PropertyNames` rather than hard-coded string literals. Apply the same pattern to any future native iterator result objects so they interoperate correctly in bytecode mode (e.g., for `Object.prototype` method behavior) and keep property-name usage consistent via the shared constants.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 136
File: docs/adding-built-in-types.md:105-133
Timestamp: 2026-03-27T14:09:25.371Z
Learning: In frostney/GocciaScript Pascal code, do not flag or suggest refactoring the early-exit singleton initialization guard `if Assigned(FShared) then Exit` when it appears at the top of `InitializePrototype`. The project documentation treats this early-exit form as the canonical/shared-prototype singleton initialization template (i.e., avoid recommending an `if not Assigned(FShared) then ...` wrapping alternative).

Comment thread units/Goccia.Evaluator.Comparison.pas
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 1, 2026

Benchmark Results

263 benchmarks

Interpreted: 🟢 170 improved · 🔴 14 regressed · 79 unchanged · avg +2.1%
Bytecode: 🟢 63 improved · 🔴 47 regressed · 153 unchanged · avg +0.7%

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

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 1, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 3503 3503
Tests Passed 3462 ✅ 3503 ✅
Tests Skipped 41 0
Tests Execution 178.2ms 167.2ms
Tests Engine 329.5ms 586.7ms
Benchmarks Total 263 263
Benchmarks Duration 7.28min 6.08min

Measured on ubuntu-latest x64.

- require own properties for toMatchObject subset checks
- add regression coverage for prototype-chain subset matching
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

🤖 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.Evaluator.Comparison.pas`:
- Around line 360-385: The recursive comparison (in IsDeepEqual /
IsPartialDeepEqual paths that handle TGocciaObjectValue pairs using AActual and
AExpected and iterating ExpectedKeys) can infinite-loop on cyclic object graphs;
add a visited-pair set keyed by the pair (AActual, AExpected) that is checked at
the start of each recursive comparison and updated before recursing so revisited
pairs immediately short-circuit to True (or the appropriate already-seen
result), and ensure both IsDeepEqual and IsPartialDeepEqual consult and update
this shared visited-pair tracking to prevent stack overflows on cycles.
🪄 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: 96db738c-4a98-4707-abb2-b780548654c0

📥 Commits

Reviewing files that changed from the base of the PR and between 45c4e2f and d76ba16.

📒 Files selected for processing (2)
  • units/Goccia.Builtins.TestAssertions.Test.pas
  • units/Goccia.Evaluator.Comparison.pas
🚧 Files skipped from review as they are similar to previous changes (1)
  • units/Goccia.Builtins.TestAssertions.Test.pas

Comment thread units/Goccia.Evaluator.Comparison.pas Outdated
- track visited value pairs in deep and partial comparisons
- add cyclic matcher regression coverage for toStrictEqual and toMatchObject
@frostney frostney merged commit 2a86d92 into main Apr 2, 2026
9 checks passed
@frostney frostney deleted the feature/vitest-phase1-matchers-117 branch April 2, 2026 20:23
@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.

1 participant