diff --git a/docs/rxjs-next/COMPATIBILITY.md b/docs/rxjs-next/COMPATIBILITY.md index b9dfc15252..e5eee51fdb 100644 --- a/docs/rxjs-next/COMPATIBILITY.md +++ b/docs/rxjs-next/COMPATIBILITY.md @@ -400,10 +400,12 @@ whether the RxJS form delegates, which additional functionality it supplies, and which behavior or types intentionally differ. It must also prove that installing the Symbol does not alter the platform method. -The P2.4 `map` pilot is the first recorded overlap. `observable[map](project, -thisArg?)` owns an RxJS projection index and optional callback receiver, -constructs through the RxJS `[create]` protocol, and participates in the -platform layer's shared activation lifecycle. It does not delegate to +The P2.4 `map` pilot is the first recorded overlap. `observable[map](project)` +owns an RxJS projection index, constructs through the RxJS `[create]` protocol, +and participates in the platform layer's shared activation lifecycle. D-059 +removes the inherited RxJS 7 callback-receiver argument from this and every +other RxJS Next callback API; migration uses a closure or +`Function.prototype.bind`. The Symbol form does not delegate to `observable.map(project)`, and installing it leaves that platform-owned string method unchanged. Focused and native/fallback kernel tests cover both the additional Symbol behavior and non-interference. diff --git a/docs/rxjs-next/DECISIONS.md b/docs/rxjs-next/DECISIONS.md index 8fa787d248..b532ed4129 100644 --- a/docs/rxjs-next/DECISIONS.md +++ b/docs/rxjs-next/DECISIONS.md @@ -476,7 +476,7 @@ Status meanings: ## D-023 — Keep RxJS map and filter overloads on exact Symbol keys -- **Status:** Accepted +- **Status:** Superseded by D-059 - **Decision:** The RxJS `map` and `filter` contracts are installed only under their exported exact Symbol keys. Their RxJS forms preserve the projection or predicate index and optional `thisArg`; `filter` also preserves Boolean @@ -492,6 +492,10 @@ Status meanings: distinction for `filter`. Projection and predicate errors terminate the shared operator activation. Concurrent observers share one upstream activation and one index sequence under the platform lifecycle. +- **Reason superseded:** RxJS Next removes callback `thisArg` parameters rather + than carrying this RxJS 7 convenience into the new API. Closures and + `Function.prototype.bind` express the same receiver capture without forcing + every operator notification through `Function.prototype.call`. ## D-024 — Let finite take cancel synchronous upstream work before its limit @@ -1491,3 +1495,21 @@ Status meanings: operational and bootstrap costs for the current sole-maintainer release. - **Scope:** Stable `9.0.0`, promotion to `latest`, and future reconsideration of registry-supported trusted publishing remain separate decisions. + +## D-059 — Remove callback `thisArg` parameters from RxJS Next + +- **Status:** Accepted +- **Decision:** RxJS Next callback APIs do not accept a separate `thisArg`. + Remove the parameter and receiver-aware overloads from Symbol-keyed `every`, + `filter`, `find`, `findIndex`, `map`, and static `partition`. Invoke their + predicates and projectors directly. +- **Rationale:** A closure or `Function.prototype.bind` expresses an intentional + receiver at the call site. Retaining `thisArg` forces RxJS to dispatch every + value through `.call`, adding API and hot-path implementation cost for a + redundant convenience inherited from RxJS 7. +- **Consequence:** Migration must rewrite a second callback-receiver argument + to a closure or bound function. The callback value/index/source arguments, + type-guard and Boolean-constructor overloads, cancellation, result + construction, and platform string-named methods are unchanged. Historical + RxJS 7 evidence retains its source identity, while active migrated specs use + closures or bound functions instead of asserting the removed overload. diff --git a/docs/rxjs-next/OPEN_QUESTIONS.md b/docs/rxjs-next/OPEN_QUESTIONS.md index 8ab79014a4..0dcc1b0b5a 100644 --- a/docs/rxjs-next/OPEN_QUESTIONS.md +++ b/docs/rxjs-next/OPEN_QUESTIONS.md @@ -108,13 +108,14 @@ Decide per overlapping operator: - documentation that prevents false parity claims; - native and fallback evidence that the platform method remains untouched. -P2.4 settles the pilot `map` case. The exact RxJS Symbol form supplies the -RxJS projection index and optional `thisArg`, constructs through `[create]`, -and follows the platform layer's shared activation contract. It does not -delegate to or replace the string-named platform `map`; native and fallback -kernel evidence verifies that the original method and descriptor remain -unchanged. Other overlapping operators still require the same per-capability -record before they are restored. +P2.4 settled the pilot `map` overlap and D-059 later removed its inherited +RxJS 7 callback-receiver argument. The exact RxJS Symbol form supplies the +RxJS projection index, constructs through `[create]`, and follows the platform +layer's shared activation contract. It does not delegate to or replace the +string-named platform `map`; native and fallback kernel evidence verifies that +the original method and descriptor remain unchanged. Other overlapping +operators still require the same per-capability record before they are +restored. ### 6. What is the canonical extension implementation pattern? diff --git a/docs/rxjs-next/PROJECT_PLAN.md b/docs/rxjs-next/PROJECT_PLAN.md index cdf47fc508..f33b06389c 100644 --- a/docs/rxjs-next/PROJECT_PLAN.md +++ b/docs/rxjs-next/PROJECT_PLAN.md @@ -80,9 +80,11 @@ wrappers in favor of direct D-037 `[create]` calls and recorded a further bundle-size reduction. P6.8 completed durable pull-request and `master` CI ownership for every accepted RxJS 9 test and release check. P6.9 implemented truthful status signals and security automation and validated their first live -GitHub results. The user has now prioritized P6.10: one understandable, -interactive single-maintainer beta publication command with npm two-factor -authentication and no CI publishing credential. +GitHub results. The user then explicitly prioritized P6.11, which removed +inherited callback `thisArg` parameters and their hot-path dispatch cost. P6.10 +remains the sole `NEXT` item: one understandable, interactive single-maintainer +beta publication command with npm two-factor authentication and no CI +publishing credential. RxJS 9 and `9.0.0-beta.0` are selected under D-007. D-053 defines runtime, browser, bundler, channel, and RxJS 7 maintenance policy. Dates and staffing @@ -1569,6 +1571,7 @@ names. | `DONE` | P6.8 | Complete RxJS 9 CI coverage and validate the resulting pull-request workflow matrix | | `DONE` | P6.9 | Validate the first live dependency-review and Scorecard runs on GitHub | | `NEXT` | P6.10 | Publish and verify the first beta with the interactive release command | +| `DONE` | P6.11 | Remove inherited callback `thisArg` parameters and direct-call callback hot paths | #### P6.9 completion bar @@ -1647,6 +1650,41 @@ review`. This completes P6.9; the later P6.10 ruleset migration must preserve OTP/WebAuthn publication, package-access hardening, registry verification, and the immutable GitHub Release remain required before `DONE`. +#### P6.11 completion bar + +- Every RxJS Next callback API is audited for a separate `thisArg`; the exact + affected API inventory is recorded. +- Receiver-aware overloads, implementation parameters, and `.call` dispatch + are removed from all affected APIs without changing platform-polyfill + receiver binding required by Web IDL and iterator protocols. +- Focused and migrated tests no longer assert library-provided callback + receivers; source-pinned RxJS 7 identifiers remain historical evidence, and + active replacements demonstrate closures or `Function.prototype.bind`. +- Focused source, migrated cold/polyfill, public type, package, kernel, + migration-document freshness, lint, and diff gates pass. + +#### P6.11 completion evidence + +- Audited RxJS production source and found exactly six APIs with a separate + callback receiver argument: Symbol-keyed `every`, `filter`, `find`, + `findIndex`, and `map`, plus static Symbol-keyed `partition`. Removed every + receiver-aware overload, implementation parameter, and callback `.call`. +- Replaced active receiver assertions with ordinary value/index/source tests, + closures, or explicitly bound callbacks. Added public declaration checks + that reject the removed second or third arguments. Classified all six + source-pinned RxJS 7 cases as intentional divergences and made their + executable migrated programs bind callbacks instead of exercising the + removed overload. +- Passed all 750 RxJS source tests, the 76 affected cold and 76 affected + polyfill migrated tests, lint with zero errors, public types, package build, + migration-document freshness, 97-Symbol installation, and all package import + fixtures. Exact complete audits retained the reviewed 2,299/39 cold and + 2,316/22 polyfill outcomes. +- Passed the eight-case packaged fallback/native Chrome 150 kernel contract. + The release performance gate measured approximately 63.0 million map values + and 140,627 cancellations per second. Marked P6.11 `DONE`; the completed + execution queue has no `NEXT` marker. + #### P6.1 completion bar - All release packages share the accepted RxJS 9 prerelease identity and exact @@ -3680,3 +3718,19 @@ conformance implementation depends on a runnable harness. remains the sole `NEXT` item until the command publishes and verifies `9.0.0-beta.0`, package access disallows automation tokens, and the immutable GitHub Release is recorded. +### 2026-08-05 — P6.11 callback receiver removal + +- Audited the complete RxJS production source and removed callback `thisArg` + overloads and `.call` dispatch from `every`, `filter`, `find`, `findIndex`, + `map`, and `partition`; no platform-polyfill protocol receiver binding was + changed. +- Reworked focused and migrated tests to use ordinary closures or bound + callbacks, added negative public-type assertions, and classified the six + source-pinned RxJS 7 receiver cases as intentional divergences while + preserving their historical IDs. +- Passed 750 source tests, both 76-test affected migrated suites, exact complete + cold/polyfill audit baselines, public types, lint, package/build/import and + migration freshness gates, the packaged fallback/native Chrome kernel, and + the release performance floor. +- Recorded D-059, marked P6.11 `DONE`, and retained P6.10 as the sole `NEXT` + item. diff --git a/docs/rxjs-next/RxJS-7-parity.md b/docs/rxjs-next/RxJS-7-parity.md index 2562077e84..ab41a48519 100644 --- a/docs/rxjs-next/RxJS-7-parity.md +++ b/docs/rxjs-next/RxJS-7-parity.md @@ -70,22 +70,22 @@ pnpm --filter rxjs run test:unit:parity:check | `distinctUntilKeyChanged` | Present; parity unverified | `source[distinctUntilKeyChanged](key, comparator?)` | 21 | Delegates property-key comparison to the exact distinctUntilChanged Symbol contract. Symbol source: `packages/rxjs/src/distinct-until-key-changed.ts`; executable adapter: `identity`. | | `elementAt` | Present; partial empty behavior | `source[elementAt](index, defaultValue?)` | 12 | The default-value overload is present; the no-default out-of-range error remains compatibility evidence. Symbol source: `packages/rxjs/src/element-at.ts`; executable adapter: `elementAt`. | | `endWith` | Partial unified mapping | `source[concat]([values])` | 16 | Adapts trailing values to one iterable concat source; a trailing SchedulerLike is retained as an explicit unsupported-overload failure. Symbol source: `packages/rxjs/src/concat.ts`; executable adapter: `endWith`. | -| `every` | Present; parity unverified | `source[every](predicate, thisArg?)` | 22 | Evaluates value, index, and source and cancels upstream before emitting the first false result. Symbol source: `packages/rxjs/src/every.ts`; executable adapter: `identity`. | +| `every` | Present; parity unverified | `source[every](predicate)` | 22 | Evaluates value, index, and source and cancels upstream before emitting the first false result. Symbol source: `packages/rxjs/src/every.ts`; executable adapter: `identity`. | | `exhaust` | Partial unified mapping | `source[exhaustMap](identity)` | 0 | Legacy flattening alias maps to exhaustMap. Symbol source: `packages/rxjs/src/exhaust-map.ts`; executable adapter: `flattenIdentity`. | | `exhaustAll` | Partial unified mapping | `source[exhaustMap](identity)` | 15 | Flattening is expressed through exhaustMap. Symbol source: `packages/rxjs/src/exhaust-map.ts`; executable adapter: `flattenIdentity`. | | `exhaustMap` | Present; parity unverified | `source[exhaustMap](project)` | 18 | Legacy result-selector overloads are not represented. Symbol source: `packages/rxjs/src/exhaust-map.ts`; executable adapter: `firstArgument`. | | `expand` | Present; scheduler overload deferred | `source[expand](project, { concurrent })` | 13 | Adapts legacy numeric concurrency to the options contract. Scheduler arguments are not a public platform contract; their ported behavioral claims use explicit test-harness timing rewrites. Symbol source: `packages/rxjs/src/expand.ts`; executable adapter: `expand`. | -| `filter` | Present; parity unverified | `source[filter](predicate, thisArg?)` | 21 | The RxJS Symbol form preserves predicate index, thisArg, type guards, errors, and cancellation without replacing the platform string method. Symbol source: `packages/rxjs/src/filter.ts`; executable adapter: `identity`. | +| `filter` | Present; parity unverified | `source[filter](predicate)` | 21 | The RxJS Symbol form preserves predicate index, type guards, errors, and cancellation without replacing the platform string method; callback receivers must use closures or Function.prototype.bind. Symbol source: `packages/rxjs/src/filter.ts`; executable adapter: `identity`. | | `finalize` | Present; parity unverified | `source[finalize](callback)` | 8 | Runs once after completion, error, or last-observer cancellation without replacing the platform finally method. Symbol source: `packages/rxjs/src/finalize.ts`; executable adapter: `identity`. | -| `find` | Present; parity unverified | `source[find](predicate, thisArg?)` | 12 | Emits the first matching value or undefined with predicate index, source identity, thisArg, early cancellation, and no replacement of the platform find() Promise consumer. Symbol source: `packages/rxjs/src/find.ts`; executable adapter: `identity`. | -| `findIndex` | Present; parity unverified | `source[findIndex](predicate, thisArg?)` | 12 | Emits the first matching index or -1 with predicate source identity, thisArg, errors, and early cancellation. Symbol source: `packages/rxjs/src/find-index.ts`; executable adapter: `identity`. | +| `find` | Present; parity unverified | `source[find](predicate)` | 12 | Emits the first matching value or undefined with predicate index, source identity, and early cancellation, without replacing the platform find() Promise consumer; callback receivers must use closures or Function.prototype.bind. Symbol source: `packages/rxjs/src/find.ts`; executable adapter: `identity`. | +| `findIndex` | Present; parity unverified | `source[findIndex](predicate)` | 12 | Emits the first matching index or -1 with predicate source identity, errors, and early cancellation; callback receivers must use closures or Function.prototype.bind. Symbol source: `packages/rxjs/src/find-index.ts`; executable adapter: `identity`. | | `first` | Present; parity unverified | `source[first](predicate?, defaultValue?)` | 17 | Preserves predicate, default-value arity, EmptyError, early cancellation, and the distinct RxJS Symbol contract without replacing the platform first() Promise consumer. Symbol source: `packages/rxjs/src/first.ts`; executable adapter: `identity`. | | `flatMap` | Partial unified mapping | `source[mergeMap](project, options?)` | 0 | RxJS 7 flatMap is an alias of mergeMap. Symbol source: `packages/rxjs/src/merge-map.ts`; executable adapter: `mergeMap`. | | `groupBy` | Present; scheduler-internal cases deferred | `source[groupBy](keySelector, optionsOrElement?, duration?, connector?)` | 36 | Emits hot read-only keyed group views with options and deprecated positional selector forms; active group observers may retain source work after outer cancellation. Symbol source: `packages/rxjs/src/group-by.ts`; executable adapter: `identity`. | | `ignoreElements` | Partial unified mapping | `source[mergeMap](() => Observable.from([]))` | 9 | Maps each input to an ambient empty Observable while retaining source completion and error behavior. Symbol source: `packages/rxjs/src/merge-map.ts`; executable adapter: `ignoreElements`. | | `isEmpty` | Present; parity unverified | `source[isEmpty]()` | 7 | Emits false and cancels upstream on the first value, or emits true after successful empty completion. Symbol source: `packages/rxjs/src/is-empty.ts`; executable adapter: `identity`. | | `last` | Present; parity unverified | `source[last](predicate?, defaultValue?)` | 11 | Preserves predicate, default-value arity, EmptyError, completion-time selection, and the distinct RxJS Symbol contract without replacing the platform last() Promise consumer. Symbol source: `packages/rxjs/src/last.ts`; executable adapter: `identity`. | -| `map` | Present; parity unverified | `source[map](project, thisArg?)` | 93 | The RxJS Symbol form preserves projection index, thisArg, errors, and cancellation without replacing the platform string method. Symbol source: `packages/rxjs/src/map.ts`; executable adapter: `identity`. | +| `map` | Present; parity unverified | `source[map](project)` | 93 | The RxJS Symbol form preserves projection index, errors, and cancellation without replacing the platform string method; callback receivers must use closures or Function.prototype.bind. Symbol source: `packages/rxjs/src/map.ts`; executable adapter: `identity`. | | `mapTo` | Partial unified mapping | `source[mergeMap](() => Observable.from([value]))` | 11 | Expresses constant projection through mergeMap and an ambient single-value Observable. Symbol source: `packages/rxjs/src/merge-map.ts`; executable adapter: `mapTo`. | | `materialize` | Present; parity unverified | `source[materialize]()` | 10 | Converts every source signal into a Notification next value and completes after materialized error or completion. Symbol source: `packages/rxjs/src/materialize.ts`; executable adapter: `identity`. | | `max` | Present; parity unverified | `source[max](comparer?)` | 18 | Delegates unseeded maximum selection and lifecycle behavior to the exact reduce Symbol. Symbol source: `packages/rxjs/src/max.ts`; executable adapter: `identity`. | @@ -178,7 +178,7 @@ pnpm --filter rxjs run test:unit:parity:check | `of` | Partial platform mapping | `Observable.from(values)` | 400 | Adapts variadic values to the platform Observable.from contract. Uses the ambient platform Observable. Executable adapter: `of`. | | `onErrorResumeNext` | Partial unified mapping | `Observable[onErrorResumeNext](sources)` | 3 | Adapts variadic or single-array source arguments to the current array form. Symbol source: `packages/rxjs/src/on-error-resume-next.ts`. Executable adapter: `staticSourcesArrayOrSingleArray`. | | `pairs` | Partial platform mapping | `Observable.from(Object.entries(object))` | 2 | Scheduler arguments are not represented. Uses the ambient platform Observable. Executable adapter: `pairs`. | -| `partition` | Present; parity unverified | `Observable[partition](source, predicate, thisArg?)` | 16 | Returns matching and rejected branches with independent predicate state over the platform source's shared, ref-counted activation. Symbol source: `packages/rxjs/src/partition.ts`. Executable adapter: `identity`. | +| `partition` | Present; parity unverified | `Observable[partition](source, predicate)` | 16 | Returns matching and rejected branches with independent predicate state over the platform source's shared, ref-counted activation. Symbol source: `packages/rxjs/src/partition.ts`. Executable adapter: `identity`. | | `race` | Partial unified mapping | `Observable[race](sources)` | 15 | Adapts variadic or single-array sources to the current array form. Symbol source: `packages/rxjs/src/race.ts`. Executable adapter: `staticSourcesArrayOrSingleArray`. | | `range` | Partial platform mapping | `Observable.from(Array.from({ length: count }, (_, i) => start + i))` | 3 | Scheduler arguments are not represented. Uses the ambient platform Observable. Executable adapter: `range`. | | `scheduled` | **Missing** | — | 5 | No current RxJS Next function mapping. | diff --git a/packages/rxjs/MIGRATION.md b/packages/rxjs/MIGRATION.md index bd4b428be5..e76852d419 100644 --- a/packages/rxjs/MIGRATION.md +++ b/packages/rxjs/MIGRATION.md @@ -78,8 +78,22 @@ const names = users[filter]((user) => user.active)[map]((user) => user.name); The platform and RxJS forms can coexist. For example, `source.map(project)` is the platform contract while -`source[map](project, thisArg)` is the RxJS contract. Importing the RxJS Symbol -must not replace the platform string-named method. +`source[map](project)` is the RxJS contract. Importing the RxJS Symbol must not +replace the platform string-named method. + +RxJS 9 does not accept the RxJS 7 callback `thisArg` parameter on `every`, +`filter`, `find`, `findIndex`, `map`, or `partition`. Capture state with a +closure or bind the callback explicitly: + +```ts +const offset = 10; +const closedOver = source[map]((value) => value + offset); + +const context = { offset: 10 }; +const bound = source[map](function (value) { + return value + this.offset; +}.bind(context)); +``` Use the exact `pipe` Symbol only when deliberate multi-step composition is clearer than direct Symbol chaining: diff --git a/packages/rxjs/docs/MIGRATION_EVIDENCE_LEDGER.md b/packages/rxjs/docs/MIGRATION_EVIDENCE_LEDGER.md index 3716f59f0f..f38d148b6a 100644 --- a/packages/rxjs/docs/MIGRATION_EVIDENCE_LEDGER.md +++ b/packages/rxjs/docs/MIGRATION_EVIDENCE_LEDGER.md @@ -62,10 +62,10 @@ pnpm --filter rxjs run test:unit:ledger:check | `exhaustAll` | Partial unified mapping | source-pinned | 15 | 15/15 | 15/15 | portable | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-003, D-048, D-049 | | `exhaustMap` | Present; parity unverified | source-pinned | 18 | 17/18 | 17/18 | compatibility-only, portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-048, D-049 | | `expand` | Present; scheduler overload deferred | source-pinned | 13 | 13/13 | 13/13 | harness-rewrite, portable | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-003, D-033, D-034, D-048, D-049 | -| `filter` | Present; parity unverified | source-pinned | 21 | 21/21 | 21/21 | portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-048, D-049 | +| `filter` | Present; parity unverified | source-pinned | 21 | 21/21 | 21/21 | intentional-divergence, portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-048, D-049 | | `finalize` | Present; parity unverified | source-pinned | 8 | 8/8 | 8/8 | portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-048, D-049 | -| `find` | Present; parity unverified | source-pinned | 12 | 12/12 | 12/12 | portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-048, D-049 | -| `findIndex` | Present; parity unverified | source-pinned | 12 | 12/12 | 12/12 | portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-048, D-049 | +| `find` | Present; parity unverified | source-pinned | 12 | 12/12 | 12/12 | intentional-divergence, portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-048, D-049 | +| `findIndex` | Present; parity unverified | source-pinned | 12 | 12/12 | 12/12 | intentional-divergence, portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-048, D-049 | | `first` | Present; parity unverified | source-pinned | 17 | 17/17 | 17/17 | portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-048, D-049 | | `flatMap` | Partial unified mapping | source-pinned-alias | 149 | 143/149 | 143/149 | compatibility-only, harness-rewrite, intentional-divergence, portable | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-003, D-048, D-049 | | `groupBy` | Present; scheduler-internal cases deferred | source-pinned | 36 | 36/36 | 36/36 | harness-rewrite, portable | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-003, D-033, D-034, D-048, D-049 | @@ -160,7 +160,7 @@ pnpm --filter rxjs run test:unit:ledger:check | `of` | Partial platform mapping | source-pinned | 400 | 394/400 | 394/400 | compatibility-only, harness-rewrite, intentional-divergence, portable | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-040 | | `onErrorResumeNext` | Partial unified mapping | source-pinned | 3 | 3/3 | 3/3 | portable | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-003, D-040, D-048, D-049 | | `pairs` | Partial platform mapping | source-pinned | 2 | 2/2 | 2/2 | portable | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-040 | -| `partition` | Present; parity unverified | source-pinned | 16 | 16/16 | 16/16 | harness-rewrite, portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-040, D-048, D-049 | +| `partition` | Present; parity unverified | source-pinned | 16 | 16/16 | 16/16 | harness-rewrite, intentional-divergence, portable | shared-active-platform | AbortSignal | changed | mechanical-change | D-003, D-040, D-048, D-049 | | `race` | Partial unified mapping | source-pinned | 15 | 15/15 | 15/15 | portable | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-003, D-040, D-048, D-049 | | `range` | Partial platform mapping | source-pinned | 3 | 3/3 | 3/3 | harness-rewrite | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-040 | | `throwError` | Partial platform mapping | source-pinned | 31 | 31/31 | 31/31 | harness-rewrite, intentional-divergence, portable | shared-active-platform | AbortSignal | changed | mechanical-change-and-semantic-review | D-040 | diff --git a/packages/rxjs/src/every.spec.ts b/packages/rxjs/src/every.spec.ts index e3c09f338e..8dd36dec9d 100644 --- a/packages/rxjs/src/every.spec.ts +++ b/packages/rxjs/src/every.spec.ts @@ -17,10 +17,12 @@ describe('every', () => { const emptyResults: Array = []; const matchingResults: Array = []; - new Observable((subscriber) => subscriber.complete())[every]((value) => value > 0).subscribe({ - next: (value) => emptyResults.push(value), - complete: () => emptyResults.push('complete'), - }); + new Observable((subscriber) => subscriber.complete()) + [every]((value) => value > 0) + .subscribe({ + next: (value) => emptyResults.push(value), + complete: () => emptyResults.push('complete'), + }); fromValues(2, 4, 6) [every]((value) => value % 2 === 0) @@ -33,17 +35,16 @@ describe('every', () => { expect(matchingResults).toEqual([true, 'complete']); }); - it('passes the value, zero-based index, source, and optional thisArg to the predicate', () => { - const context = { maximum: 10 }; + it('passes the value, zero-based index, and source to the predicate', () => { + const maximum = 10; const source = fromValues(2, 4, 6); const calls: Array<[number, number, Observable]> = []; const results: boolean[] = []; - source[every](function (value, index, predicateSource) { - expect(this).toBe(context); + source[every]((value, index, predicateSource) => { calls.push([value, index, predicateSource]); - return value < this.maximum; - }, context).subscribe((value) => results.push(value)); + return value < maximum; + }).subscribe((value) => results.push(value)); expect(calls).toEqual([ [2, 0, source], diff --git a/packages/rxjs/src/every.ts b/packages/rxjs/src/every.ts index 30a1528ea3..a4ecaedd77 100644 --- a/packages/rxjs/src/every.ts +++ b/packages/rxjs/src/every.ts @@ -9,25 +9,16 @@ declare global { interface Observable { [every]: { (predicate: BooleanConstructor): Observable extends never ? false : boolean>; - (predicate: BooleanConstructor, thisArg: unknown): Observable extends never ? false : boolean>; - (predicate: (this: A, value: T, index: number, source: Observable) => boolean, thisArg: A): Observable; (predicate: (value: T, index: number, source: Observable) => boolean): Observable; }; } } function everyOperator(this: Observable, predicate: BooleanConstructor): Observable; -function everyOperator(this: Observable, predicate: BooleanConstructor, thisArg: unknown): Observable; -function everyOperator( - this: Observable, - predicate: (this: A, value: T, index: number, source: Observable) => boolean, - thisArg: A -): Observable; function everyOperator(this: Observable, predicate: (value: T, index: number, source: Observable) => boolean): Observable; function everyOperator( this: Observable, - predicate: (this: unknown, value: T, index: number, source: Observable) => boolean, - thisArg?: unknown + predicate: (value: T, index: number, source: Observable) => boolean ): Observable { const source = this; @@ -46,7 +37,7 @@ function everyOperator( subscriber, { next: (value) => { - if (!predicate.call(thisArg, value, index++, source)) { + if (!predicate(value, index++, source)) { conclude(false); } }, diff --git a/packages/rxjs/src/filter.spec.ts b/packages/rxjs/src/filter.spec.ts index 9d43d7230e..9ef067a95e 100644 --- a/packages/rxjs/src/filter.spec.ts +++ b/packages/rxjs/src/filter.spec.ts @@ -3,8 +3,8 @@ import '@rxjs/observable-polyfill'; import { filter } from './filter.js'; describe('filter', () => { - it('emits accepted values and supplies a zero-based index and thisArg', () => { - const context = { minimum: 2 }; + it('emits accepted values and supplies a zero-based index', () => { + const minimum = 2; const calls: Array<[number, number]> = []; const results: Array = []; const source = new Observable((subscriber) => { @@ -14,10 +14,10 @@ describe('filter', () => { subscriber.complete(); }); - source[filter](function (value, index) { + source[filter]((value, index) => { calls.push([value, index]); - return value >= this.minimum; - }, context).subscribe({ + return value >= minimum; + }).subscribe({ next: (value) => results.push(value), complete: () => results.push('complete'), }); diff --git a/packages/rxjs/src/filter.ts b/packages/rxjs/src/filter.ts index e5a83bbcca..f4a58cf060 100644 --- a/packages/rxjs/src/filter.ts +++ b/packages/rxjs/src/filter.ts @@ -9,35 +9,23 @@ export const filter: unique symbol = Symbol('filter'); declare global { interface Observable { [filter]: { - (predicate: (this: A, value: T, index: number) => value is S, thisArg: A): Observable; (predicate: (value: T, index: number) => value is S): Observable; (predicate: BooleanConstructor): Observable>; - (predicate: (this: A, value: T, index: number) => boolean, thisArg: A): Observable; (predicate: (value: T, index: number) => boolean): Observable; }; } } -function filterOperator( - this: Observable, - predicate: (this: A, value: T, index: number) => value is S, - thisArg: A -): Observable; function filterOperator(this: Observable, predicate: (value: T, index: number) => value is S): Observable; function filterOperator(this: Observable, predicate: BooleanConstructor): Observable>; -function filterOperator(this: Observable, predicate: (this: A, value: T, index: number) => boolean, thisArg: A): Observable; function filterOperator(this: Observable, predicate: (value: T, index: number) => boolean): Observable; -function filterOperator( - this: Observable, - predicate: (this: unknown, value: T, index: number) => boolean, - thisArg?: unknown -): Observable { +function filterOperator(this: Observable, predicate: (value: T, index: number) => boolean): Observable { return this[create]((subscriber) => { let index = 0; subscribeToSource(this, subscriber, { next: (value) => { - if (predicate.call(thisArg, value, index++)) { + if (predicate(value, index++)) { subscriber.next(value); } }, diff --git a/packages/rxjs/src/find-index.spec.ts b/packages/rxjs/src/find-index.spec.ts index 8759273714..180fd42d5d 100644 --- a/packages/rxjs/src/find-index.spec.ts +++ b/packages/rxjs/src/find-index.spec.ts @@ -58,7 +58,9 @@ describe('findIndex', () => { const emptyValues: number[] = []; const nonmatchingValues: number[] = []; - Observable.from([] as number[])[findIndex](() => true).subscribe((value) => emptyValues.push(value)); + Observable.from([] as number[]) + [findIndex](() => true) + .subscribe((value) => emptyValues.push(value)); Observable.from([1, 2, 3]) [findIndex]((value) => value > 10) .subscribe((value) => nonmatchingValues.push(value)); @@ -67,21 +69,21 @@ describe('findIndex', () => { expect(nonmatchingValues).toEqual([-1]); }); - it('passes value, index, and the exact source and supports thisArg', () => { + it('passes value, index, and the exact source', () => { const source = Observable.from(['a', 'bb', 'ccc']); - const context = { length: 2 }; - const calls: Array<[string, number, Observable, unknown]> = []; + const length = 2; + const calls: Array<[string, number, Observable]> = []; const values: number[] = []; - source[findIndex](function (value, index, receivedSource) { - calls.push([value, index, receivedSource, this]); - return value.length === this.length; - }, context).subscribe((value) => values.push(value)); + source[findIndex]((value, index, receivedSource) => { + calls.push([value, index, receivedSource]); + return value.length === length; + }).subscribe((value) => values.push(value)); expect(values).toEqual([1]); expect(calls).toEqual([ - ['a', 0, source, context], - ['bb', 1, source, context], + ['a', 0, source], + ['bb', 1, source], ]); }); @@ -92,9 +94,11 @@ describe('findIndex', () => { const predicateErrors: unknown[] = []; const produced: number[] = []; - new Observable((subscriber) => subscriber.error(sourceFailure))[findIndex](() => true).subscribe({ - error: (error) => sourceErrors.push(error), - }); + new Observable((subscriber) => subscriber.error(sourceFailure)) + [findIndex](() => true) + .subscribe({ + error: (error) => sourceErrors.push(error), + }); new Observable((subscriber) => { for (const value of [1, 2, 3]) { if (!subscriber.active) { diff --git a/packages/rxjs/src/find-index.ts b/packages/rxjs/src/find-index.ts index 42c27e95fe..1cbadb8683 100644 --- a/packages/rxjs/src/find-index.ts +++ b/packages/rxjs/src/find-index.ts @@ -6,25 +6,14 @@ export const findIndex: unique symbol = Symbol('findIndex'); declare global { interface Observable { [findIndex]: { - (predicate: (this: A, value: T, index: number, source: Observable) => boolean, thisArg: A): Observable; (predicate: (value: T, index: number, source: Observable) => boolean): Observable; }; } } -function findIndexOperator( - this: Observable, - predicate: (this: A, value: T, index: number, source: Observable) => boolean, - thisArg: A -): Observable; function findIndexOperator( this: Observable, predicate: (value: T, index: number, source: Observable) => boolean -): Observable; -function findIndexOperator( - this: Observable, - predicate: (this: unknown, value: T, index: number, source: Observable) => boolean, - thisArg?: unknown ): Observable { const source = this; @@ -44,7 +33,7 @@ function findIndexOperator( { next: (value) => { const currentIndex = index++; - if (predicate.call(thisArg, value, currentIndex, source)) { + if (predicate(value, currentIndex, source)) { conclude(currentIndex); } }, diff --git a/packages/rxjs/src/find.spec.ts b/packages/rxjs/src/find.spec.ts index 37c15e9ef5..7842991261 100644 --- a/packages/rxjs/src/find.spec.ts +++ b/packages/rxjs/src/find.spec.ts @@ -56,7 +56,9 @@ describe('find', () => { const emptyValues: Array = []; const nonmatchingValues: Array = []; - Observable.from([] as number[])[find](() => true).subscribe((value) => emptyValues.push(value)); + Observable.from([] as number[]) + [find](() => true) + .subscribe((value) => emptyValues.push(value)); Observable.from([1, 2, 3]) [find]((value) => value > 10) .subscribe((value) => nonmatchingValues.push(value)); @@ -65,21 +67,21 @@ describe('find', () => { expect(nonmatchingValues).toEqual([undefined]); }); - it('passes value, index, and the exact source and supports thisArg', () => { + it('passes value, index, and the exact source', () => { const source = Observable.from([2, 4, 6]); - const context = { target: 4 }; - const calls: Array<[number, number, Observable, unknown]> = []; + const target = 4; + const calls: Array<[number, number, Observable]> = []; const values: Array = []; - source[find](function (value, index, receivedSource) { - calls.push([value, index, receivedSource, this]); - return value === this.target; - }, context).subscribe((value) => values.push(value)); + source[find]((value, index, receivedSource) => { + calls.push([value, index, receivedSource]); + return value === target; + }).subscribe((value) => values.push(value)); expect(values).toEqual([4]); expect(calls).toEqual([ - [2, 0, source, context], - [4, 1, source, context], + [2, 0, source], + [4, 1, source], ]); }); @@ -97,9 +99,11 @@ describe('find', () => { const predicateErrors: unknown[] = []; const produced: number[] = []; - new Observable((subscriber) => subscriber.error(sourceFailure))[find](() => true).subscribe({ - error: (error) => sourceErrors.push(error), - }); + new Observable((subscriber) => subscriber.error(sourceFailure)) + [find](() => true) + .subscribe({ + error: (error) => sourceErrors.push(error), + }); new Observable((subscriber) => { for (const value of [1, 2, 3]) { if (!subscriber.active) { diff --git a/packages/rxjs/src/find.ts b/packages/rxjs/src/find.ts index ed2f58ee67..6f69343ab7 100644 --- a/packages/rxjs/src/find.ts +++ b/packages/rxjs/src/find.ts @@ -6,38 +6,23 @@ export const find: unique symbol = Symbol('find'); declare global { interface Observable { [find]: { - (predicate: (this: A, value: T, index: number, source: Observable) => value is S, thisArg: A): Observable< - S | undefined - >; (predicate: (value: T, index: number, source: Observable) => value is S): Observable; - (predicate: (this: A, value: T, index: number, source: Observable) => boolean, thisArg: A): Observable; (predicate: (value: T, index: number, source: Observable) => boolean): Observable; }; } } -function findOperator( - this: Observable, - predicate: (this: A, value: T, index: number, source: Observable) => value is S, - thisArg: A -): Observable; function findOperator( this: Observable, predicate: (value: T, index: number, source: Observable) => value is S ): Observable; -function findOperator( - this: Observable, - predicate: (this: A, value: T, index: number, source: Observable) => boolean, - thisArg: A -): Observable; function findOperator( this: Observable, predicate: (value: T, index: number, source: Observable) => boolean ): Observable; function findOperator( this: Observable, - predicate: (this: unknown, value: T, index: number, source: Observable) => boolean, - thisArg?: unknown + predicate: (value: T, index: number, source: Observable) => boolean ): Observable { const source = this; @@ -56,7 +41,7 @@ function findOperator( subscriber, { next: (value) => { - if (predicate.call(thisArg, value, index++, source)) { + if (predicate(value, index++, source)) { conclude(value); } }, diff --git a/packages/rxjs/src/map.spec.ts b/packages/rxjs/src/map.spec.ts index 5e6bb6cd8f..93a0b13b4a 100644 --- a/packages/rxjs/src/map.spec.ts +++ b/packages/rxjs/src/map.spec.ts @@ -43,20 +43,6 @@ describe('map', () => { expect(results).toEqual(['0:20', '1:40', '2:60', 'complete']); }); - it('supports the RxJS 7 thisArg projection contract', () => { - const context = { offset: 10 }; - const results: number[] = []; - - fromValues(1, 2) - [map](function (this: typeof context, value, index) { - expect(this).toBe(context); - return this.offset + value + index; - }, context) - .subscribe((value) => results.push(value)); - - expect(results).toEqual([11, 13]); - }); - it('does not invoke the projector for an empty source and forwards completion', () => { const project = () => { throw new Error('project should not run'); diff --git a/packages/rxjs/src/map.ts b/packages/rxjs/src/map.ts index 000e67cd95..e25577509a 100644 --- a/packages/rxjs/src/map.ts +++ b/packages/rxjs/src/map.ts @@ -7,24 +7,17 @@ declare global { interface Observable { [map]: { (project: (value: T, index: number) => R): Observable; - (project: (this: A, value: T, index: number) => R, thisArg: A): Observable; }; } } -function mapOperator(this: Observable, project: (value: T, index: number) => R): Observable; -function mapOperator(this: Observable, project: (this: A, value: T, index: number) => R, thisArg: A): Observable; -function mapOperator( - this: Observable, - project: (this: A | undefined, value: T, index: number) => R, - thisArg?: A -): Observable { +function mapOperator(this: Observable, project: (value: T, index: number) => R): Observable { return this[create]((subscriber) => { let index = 0; subscribeToSource(this, subscriber, { next(value) { - subscriber.next(project.call(thisArg, value, index++)); + subscriber.next(project(value, index++)); }, }); }); diff --git a/packages/rxjs/src/partition.spec.ts b/packages/rxjs/src/partition.spec.ts index 1b92772859..4926063d25 100644 --- a/packages/rxjs/src/partition.spec.ts +++ b/packages/rxjs/src/partition.spec.ts @@ -24,15 +24,11 @@ describe('partition', () => { it('splits an ObservableValue and gives each branch an independent index', () => { const calls: Array<[string, number]> = []; - const context = { minimumLength: 2 }; - const [matching, rejected] = Observable[partition]( - ['a', 'bb', 'ccc'], - function (value, index) { - calls.push([value, index]); - return value.length >= this.minimumLength; - }, - context - ); + const minimumLength = 2; + const [matching, rejected] = Observable[partition](['a', 'bb', 'ccc'], (value, index) => { + calls.push([value, index]); + return value.length >= minimumLength; + }); const matchingEvents: Array = []; const rejectedEvents: Array = []; diff --git a/packages/rxjs/src/partition.ts b/packages/rxjs/src/partition.ts index ce90fa7384..1dc1bf027b 100644 --- a/packages/rxjs/src/partition.ts +++ b/packages/rxjs/src/partition.ts @@ -2,15 +2,10 @@ import { create } from './create.js'; import { subscribeToSource } from './util/observable-helpers.js'; interface PartitionMethod { - (source: ObservableValue, predicate: (this: A, value: T, index: number) => value is U, thisArg: A): [ - Observable, - Observable> - ]; (source: ObservableValue, predicate: (value: T, index: number) => value is U): [ Observable, Observable> ]; - (source: ObservableValue, predicate: (this: A, value: T, index: number) => boolean, thisArg: A): [Observable, Observable]; (source: ObservableValue, predicate: (value: T, index: number) => boolean): [Observable, Observable]; } @@ -22,34 +17,17 @@ declare global { } } -function partitionImpl( - this: ObservableCtor, - source: ObservableValue, - predicate: (this: A, value: T, index: number) => value is U, - thisArg: A -): [Observable, Observable>]; function partitionImpl( this: ObservableCtor, source: ObservableValue, predicate: (value: T, index: number) => value is U ): [Observable, Observable>]; -function partitionImpl( - this: ObservableCtor, - source: ObservableValue, - predicate: (this: A, value: T, index: number) => boolean, - thisArg: A -): [Observable, Observable]; function partitionImpl( this: ObservableCtor, source: ObservableValue, predicate: (value: T, index: number) => boolean ): [Observable, Observable]; -function partitionImpl( - this: ObservableCtor, - source: ObservableValue, - predicate: (this: A, value: T, index: number) => boolean, - thisArg?: A -): any { +function partitionImpl(this: ObservableCtor, source: ObservableValue, predicate: (value: T, index: number) => boolean): any { const ObservableCtor = this; const input = ObservableCtor.from(source); @@ -59,7 +37,7 @@ function partitionImpl( subscribeToSource(input, subscriber, { next: (value) => { - if (predicate.call(thisArg as A, value, index++) === matchesBranch) { + if (predicate(value, index++) === matchesBranch) { subscriber.next(value); } }, diff --git a/packages/rxjs/test/kernel/contract.mjs b/packages/rxjs/test/kernel/contract.mjs index d996f30c70..6ee938faec 100644 --- a/packages/rxjs/test/kernel/contract.mjs +++ b/packages/rxjs/test/kernel/contract.mjs @@ -98,13 +98,9 @@ export async function runExtensionKernelContract({ ObservableCtor, platformMap, assert(!Object.hasOwn(ObservableCtor, 'timer'), 'RxJS installed string-named timer'); completeCase('installation and platform overlap'); - const context = { offset: 10 }; - const mapped = await collect( - fromValues(1, 2, 3)[symbols.map](function (value, index) { - return this.offset + value + index; - }, context) - ); - assertDeepEqual(mapped, [11, 13, 15], 'RxJS map index/thisArg behavior changed'); + const offset = 10; + const mapped = await collect(fromValues(1, 2, 3)[symbols.map]((value, index) => offset + value + index)); + assertDeepEqual(mapped, [11, 13, 15], 'RxJS map index behavior changed'); const scanned = await collect(fromValues(1, 2, 3)[symbols.scan]((total, value) => total + value, 0)); assertDeepEqual(scanned, [1, 3, 6], 'RxJS scan behavior changed'); completeCase('map and scan semantics'); diff --git a/packages/rxjs/test/ported/capability-registry.json b/packages/rxjs/test/ported/capability-registry.json index 62ba39839b..b46ec3652a 100644 --- a/packages/rxjs/test/ported/capability-registry.json +++ b/packages/rxjs/test/ported/capability-registry.json @@ -229,7 +229,7 @@ "symbol": "every", "module": "every", "adapter": "identity", - "mapping": "source[every](predicate, thisArg?)", + "mapping": "source[every](predicate)", "status": "Present; parity unverified", "note": "Evaluates value, index, and source and cancels upstream before emitting the first false result." }, @@ -285,25 +285,25 @@ "symbol": "filter", "module": "filter", "adapter": "identity", - "mapping": "source[filter](predicate, thisArg?)", + "mapping": "source[filter](predicate)", "status": "Present; parity unverified", - "note": "The RxJS Symbol form preserves predicate index, thisArg, type guards, errors, and cancellation without replacing the platform string method." + "note": "The RxJS Symbol form preserves predicate index, type guards, errors, and cancellation without replacing the platform string method; callback receivers must use closures or Function.prototype.bind." }, "find": { "symbol": "find", "module": "find", "adapter": "identity", - "mapping": "source[find](predicate, thisArg?)", + "mapping": "source[find](predicate)", "status": "Present; parity unverified", - "note": "Emits the first matching value or undefined with predicate index, source identity, thisArg, early cancellation, and no replacement of the platform find() Promise consumer." + "note": "Emits the first matching value or undefined with predicate index, source identity, and early cancellation, without replacing the platform find() Promise consumer; callback receivers must use closures or Function.prototype.bind." }, "findIndex": { "symbol": "findIndex", "module": "find-index", "adapter": "identity", - "mapping": "source[findIndex](predicate, thisArg?)", + "mapping": "source[findIndex](predicate)", "status": "Present; parity unverified", - "note": "Emits the first matching index or -1 with predicate source identity, thisArg, errors, and early cancellation." + "note": "Emits the first matching index or -1 with predicate source identity, errors, and early cancellation; callback receivers must use closures or Function.prototype.bind." }, "flatMap": { "symbol": "mergeMap", @@ -357,9 +357,9 @@ "symbol": "map", "module": "map", "adapter": "identity", - "mapping": "source[map](project, thisArg?)", + "mapping": "source[map](project)", "status": "Present; parity unverified", - "note": "The RxJS Symbol form preserves projection index, thisArg, errors, and cancellation without replacing the platform string method." + "note": "The RxJS Symbol form preserves projection index, errors, and cancellation without replacing the platform string method; callback receivers must use closures or Function.prototype.bind." }, "mapTo": { "symbol": "mergeMap", @@ -1028,7 +1028,7 @@ "symbol": "partition", "module": "partition", "adapter": "identity", - "mapping": "Observable[partition](source, predicate, thisArg?)", + "mapping": "Observable[partition](source, predicate)", "status": "Present; parity unverified", "note": "Returns matching and rejected branches with independent predicate state over the platform source's shared, ref-counted activation." }, diff --git a/packages/rxjs/test/ported/cold/observables/partition.spec.ts b/packages/rxjs/test/ported/cold/observables/partition.spec.ts index 15fb60e0b9..411907fff9 100644 --- a/packages/rxjs/test/ported/cold/observables/partition.spec.ts +++ b/packages/rxjs/test/ported/cold/observables/partition.spec.ts @@ -64,7 +64,7 @@ describe('partition (cold)', () => { expectSubscriptions(e1.subscriptions).toBe([e1subs, e1subs]); }); }); - it('should partition an observable into two using a predicate and thisArg', async () => { + it('should partition an observable into two using a bound predicate', async () => { await rxTest(({ hot, expectSubscriptions, expectObservable }) => { const e1 = hot(' --a-b---a------d--a---c--|'); const e1subs = ' ^------------------------!'; @@ -80,7 +80,7 @@ describe('partition (cold)', () => { for (let index_1 = 0; index_1 < result_1.length; index_1++) { expectObservable(result_1[index_1]).toBe(expected_1[index_1]); } - })(ColdObservable[partition](e1, predicate, { value: 'a' }), expected); + })(ColdObservable[partition](e1, predicate.bind({ value: 'a' })), expected); expectSubscriptions(e1.subscriptions).toBe([e1subs, e1subs]); }); }); diff --git a/packages/rxjs/test/ported/cold/operators/filter.spec.ts b/packages/rxjs/test/ported/cold/operators/filter.spec.ts index 16ecc75568..e66c66de1b 100644 --- a/packages/rxjs/test/ported/cold/operators/filter.spec.ts +++ b/packages/rxjs/test/ported/cold/operators/filter.spec.ts @@ -364,7 +364,7 @@ describe('filter (cold)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should be able to accept and use a thisArg', async () => { + it('should be able to use closed-over predicates', async () => { function oddFilter(x) { return +x % 2 === 1; } @@ -389,15 +389,7 @@ describe('filter (cold)', () => { filter2 = (x) => +x % 3 === 0; } const filterer = new Filterer(); - const result = e1[filter](function (x) { - return this.filter1(x); - }, filterer) - [filter](function (x) { - return this.filter2(x); - }, filterer) - [filter](function (x) { - return this.filter1(x); - }, filterer); + const result = e1[filter](filterer.filter1)[filter](filterer.filter2)[filter](filterer.filter1); expectObservable(result).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); diff --git a/packages/rxjs/test/ported/cold/operators/find-index.spec.ts b/packages/rxjs/test/ported/cold/operators/find-index.spec.ts index c84b7edff1..b12bceb589 100644 --- a/packages/rxjs/test/ported/cold/operators/find-index.spec.ts +++ b/packages/rxjs/test/ported/cold/operators/find-index.spec.ts @@ -78,7 +78,7 @@ describe('findIndex (cold)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should work with a custom thisArg', async () => { + it('should work with a bound predicate', async () => { function truePredicate(x) { return true; } @@ -90,7 +90,7 @@ describe('findIndex (cold)', () => { const predicate = function (value) { return value === this.b; }; - const result = e1[findIndex](predicate, sourceValues); + const result = e1[findIndex](predicate.bind(sourceValues)); expectObservable(result).toBe(expected, { x: 1 }); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); diff --git a/packages/rxjs/test/ported/cold/operators/find.spec.ts b/packages/rxjs/test/ported/cold/operators/find.spec.ts index d031a5c432..39cdade792 100644 --- a/packages/rxjs/test/ported/cold/operators/find.spec.ts +++ b/packages/rxjs/test/ported/cold/operators/find.spec.ts @@ -78,7 +78,7 @@ describe('find (cold)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should work with a custom thisArg', async () => { + it('should work with a bound predicate', async () => { function truePredicate(x) { return true; } @@ -92,7 +92,7 @@ describe('find (cold)', () => { const predicate = function (value) { return value === this.target; }; - expectObservable(e1[find](predicate, finder)).toBe(expected); + expectObservable(e1[find](predicate.bind(finder))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); diff --git a/packages/rxjs/test/ported/cold/operators/map.spec.ts b/packages/rxjs/test/ported/cold/operators/map.spec.ts index 8b934a2d91..13ee2a108c 100644 --- a/packages/rxjs/test/ported/cold/operators/map.spec.ts +++ b/packages/rxjs/test/ported/cold/operators/map.spec.ts @@ -170,7 +170,7 @@ describe('map (cold)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should map using a custom thisArg', async () => { + it('should map using a bound projector', async () => { const addDrama = (x) => x + '!'; await rxTest(({ hot, expectObservable, expectSubscriptions }) => { const e1 = hot('-5-^-4--3---2----1--|'); @@ -180,10 +180,12 @@ describe('map (cold)', () => { const foo = { value: 42, }; - const result = e1[map](function (x, index) { - expect(this).toBe(foo); - return parseInt(x) + foo.value + index * 10; - }, foo); + const result = e1[map]( + function (x, index) { + expect(this).toBe(foo); + return parseInt(x) + foo.value + index * 10; + }.bind(foo) + ); expectObservable(result).toBe(expected, values); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); @@ -215,7 +217,7 @@ describe('map (cold)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should do multiple maps using a custom thisArg', async () => { + it('should do multiple maps using closed-over projectors', async () => { const addDrama = (x) => x + '!'; await rxTest(({ hot, expectObservable, expectSubscriptions }) => { const e1 = hot(' --1--2--3--4--|'); @@ -227,15 +229,7 @@ describe('map (cold)', () => { selector2 = (x) => parseInt(x) * 3; } const filterer = new Filterer(); - const result = e1[map](function (x) { - return this.selector1(x); - }, filterer) - [map](function (x) { - return this.selector2(x); - }, filterer) - [map](function (x) { - return this.selector1(x); - }, filterer); + const result = e1[map](filterer.selector1)[map](filterer.selector2)[map](filterer.selector1); expectObservable(result).toBe(expected, values); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); diff --git a/packages/rxjs/test/ported/manifest.generated.json b/packages/rxjs/test/ported/manifest.generated.json index 0f00c2df3e..e7742d0a10 100644 --- a/packages/rxjs/test/ported/manifest.generated.json +++ b/packages/rxjs/test/ported/manifest.generated.json @@ -13741,14 +13741,14 @@ "title": "should partition an observable into two using a predicate and thisArg" }, "behavioralClaim": "should partition an observable into two using a predicate and thisArg", - "classification": "portable", + "classification": "intentional-divergence", "disposition": "active", "modes": [ "cold", "polyfill", "native" ], - "reason": "Mechanically migrated and verified against the ColdObservable mode.", + "reason": "Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec binds the predicate explicitly.", "duplicateOf": null, "imports": [ { @@ -13763,7 +13763,7 @@ "expectSubscriptions" ], "reviewFlags": [], - "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, partition } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n await rxTest(async ({ hot, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, expectObservable, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot(' --a-b---a------d--a---c--|');\n const e1subs = ' ^------------------------!';\n // prettier-ignore\n const expected = [\n ' --a-----a---------a------|',\n ' ----b----------d------c--|',\n ];\n function predicate(x) {\n return x === this.value;\n }\n ((result_1, expected_1) => {\n for (let index_1 = 0; index_1 < result_1.length; index_1++) {\n expectObservable(result_1[index_1]).toBe(expected_1[index_1]);\n }\n })(partition(e1, predicate, { value: 'a' }), expected);\n expectSubscriptions(e1.subscriptions).toBe([e1subs, e1subs]);\n });\n}\n", + "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, partition } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n await rxTest(async ({ hot, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, expectObservable, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot(' --a-b---a------d--a---c--|');\n const e1subs = ' ^------------------------!';\n // prettier-ignore\n const expected = [\n ' --a-----a---------a------|',\n ' ----b----------d------c--|',\n ];\n function predicate(x) {\n return x === this.value;\n }\n ((result_1, expected_1) => {\n for (let index_1 = 0; index_1 < result_1.length; index_1++) {\n expectObservable(result_1[index_1]).toBe(expected_1[index_1]);\n }\n })(partition(e1, predicate.bind({ value: 'a' })), expected);\n expectSubscriptions(e1.subscriptions).toBe([e1subs, e1subs]);\n });\n}\n", "originalSource": "it('should partition an observable into two using a predicate and thisArg', () => {\n rxTestScheduler.run(({ hot, expectSubscriptions }) => {\n const e1 = hot(' --a-b---a------d--a---c--|');\n const e1subs = ' ^------------------------!';\n // prettier-ignore\n const expected = [\n ' --a-----a---------a------|',\n ' ----b----------d------c--|',\n ];\n\n function predicate(this: any, x: string) {\n return x === this.value;\n }\n\n expectObservableArray(partition(e1, predicate, { value: 'a' }), expected);\n expectSubscriptions(e1.subscriptions).toBe([e1subs, e1subs]);\n });\n })" }, { @@ -43938,14 +43938,14 @@ "title": "should be able to accept and use a thisArg" }, "behavioralClaim": "should be able to accept and use a thisArg", - "classification": "portable", + "classification": "intentional-divergence", "disposition": "active", "modes": [ "cold", "polyfill", "native" ], - "reason": "Mechanically migrated and verified against the ColdObservable mode.", + "reason": "Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec uses closed-over predicates.", "duplicateOf": null, "imports": [ { @@ -43961,7 +43961,7 @@ "expectSubscriptions" ], "reviewFlags": [], - "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, filter } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n function oddFilter(x) {\n return +x % 2 === 1;\n }\n function isPrime(i) {\n if (+i <= 1) {\n return false;\n }\n const max = Math.floor(Math.sqrt(+i));\n for (let j = 2; j <= max; ++j) {\n if (+i % j === 0) {\n return false;\n }\n }\n return true;\n }\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot('-1--2--^-3-4-5-6--7-8--9--|');\n const e1subs = ' ^------------------!';\n const expected = ' --------6----------|';\n class Filterer {\n filter1 = (x) => +x % 2 === 0;\n filter2 = (x) => +x % 3 === 0;\n }\n const filterer = new Filterer();\n const result = applyOperators(e1, [filter(function (x) {\n return this.filter1(x);\n }, filterer),\n filter(function (x) {\n return this.filter2(x);\n }, filterer),\n filter(function (x) {\n return this.filter1(x);\n }, filterer)]);\n expectObservable(result).toBe(expected);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", + "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, filter } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n function oddFilter(x) {\n return +x % 2 === 1;\n }\n function isPrime(i) {\n if (+i <= 1) {\n return false;\n }\n const max = Math.floor(Math.sqrt(+i));\n for (let j = 2; j <= max; ++j) {\n if (+i % j === 0) {\n return false;\n }\n }\n return true;\n }\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot('-1--2--^-3-4-5-6--7-8--9--|');\n const e1subs = ' ^------------------!';\n const expected = ' --------6----------|';\n class Filterer {\n filter1 = (x) => +x % 2 === 0;\n filter2 = (x) => +x % 3 === 0;\n }\n const filterer = new Filterer();\n const result = applyOperators(e1, [filter(function (x) {\n return this.filter1(x);\n }.bind(filterer)),\n filter(function (x) {\n return this.filter2(x);\n }.bind(filterer)),\n filter(function (x) {\n return this.filter1(x);\n }.bind(filterer))]);\n expectObservable(result).toBe(expected);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", "originalSource": "it('should be able to accept and use a thisArg', () => {\n testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {\n const e1 = hot('-1--2--^-3-4-5-6--7-8--9--|');\n const e1subs = ' ^------------------!';\n const expected = ' --------6----------|';\n\n class Filterer {\n filter1 = (x: string) => +x % 2 === 0;\n filter2 = (x: string) => +x % 3 === 0;\n }\n\n const filterer = new Filterer();\n\n const result = e1.pipe(\n filter(function (this: any, x) {\n return this.filter1(x);\n }, filterer),\n filter(function (this: any, x) {\n return this.filter2(x);\n }, filterer),\n filter(function (this: any, x) {\n return this.filter1(x);\n }, filterer)\n );\n\n expectObservable(result).toBe(expected);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n })" }, { @@ -44890,14 +44890,14 @@ "title": "should work with a custom thisArg" }, "behavioralClaim": "should work with a custom thisArg", - "classification": "portable", + "classification": "intentional-divergence", "disposition": "active", "modes": [ "cold", "polyfill", "native" ], - "reason": "Mechanically migrated and verified against the ColdObservable mode.", + "reason": "Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec binds the predicate explicitly.", "duplicateOf": null, "imports": [ { @@ -44913,7 +44913,7 @@ "expectSubscriptions" ], "reviewFlags": [], - "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, find } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n function truePredicate(x) {\n return true;\n }\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot(' --a--b---c-|');\n const e1subs = ' ^----! ';\n const expected = '-----(b|) ';\n const finder = {\n target: 'b',\n };\n const predicate = function (value) {\n return value === this.target;\n };\n expectObservable(applyOperators(e1, [find(predicate, finder)])).toBe(expected);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", + "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, find } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n function truePredicate(x) {\n return true;\n }\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot(' --a--b---c-|');\n const e1subs = ' ^----! ';\n const expected = '-----(b|) ';\n const finder = {\n target: 'b',\n };\n const predicate = function (value) {\n return value === this.target;\n };\n expectObservable(applyOperators(e1, [find(predicate.bind(finder))])).toBe(expected);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", "originalSource": "it('should work with a custom thisArg', () => {\n testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {\n const e1 = hot(' --a--b---c-|');\n const e1subs = ' ^----! ';\n const expected = '-----(b|) ';\n\n const finder = {\n target: 'b',\n };\n const predicate = function (this: typeof finder, value: string) {\n return value === this.target;\n };\n\n expectObservable(e1.pipe(find(predicate, finder))).toBe(expected);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n })" }, { @@ -45377,14 +45377,14 @@ "title": "should work with a custom thisArg" }, "behavioralClaim": "should work with a custom thisArg", - "classification": "portable", + "classification": "intentional-divergence", "disposition": "active", "modes": [ "cold", "polyfill", "native" ], - "reason": "Mechanically migrated and verified against the ColdObservable mode.", + "reason": "Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec binds the predicate explicitly.", "duplicateOf": null, "imports": [ { @@ -45400,7 +45400,7 @@ "expectSubscriptions" ], "reviewFlags": [], - "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, findIndex } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n function truePredicate(x) {\n return true;\n }\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const sourceValues = { b: 7 };\n const e1 = hot(' --a--b---c-|', sourceValues);\n const e1subs = ' ^----! ';\n const expected = '-----(x|) ';\n const predicate = function (value) {\n return value === this.b;\n };\n const result = applyOperators(e1, [findIndex(predicate, sourceValues)]);\n expectObservable(result).toBe(expected, { x: 1 });\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", + "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, findIndex } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n function truePredicate(x) {\n return true;\n }\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const sourceValues = { b: 7 };\n const e1 = hot(' --a--b---c-|', sourceValues);\n const e1subs = ' ^----! ';\n const expected = '-----(x|) ';\n const predicate = function (value) {\n return value === this.b;\n };\n const result = applyOperators(e1, [findIndex(predicate.bind(sourceValues))]);\n expectObservable(result).toBe(expected, { x: 1 });\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", "originalSource": "it('should work with a custom thisArg', () => {\n testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {\n const sourceValues = { b: 7 };\n const e1 = hot(' --a--b---c-|', sourceValues);\n const e1subs = ' ^----! ';\n const expected = '-----(x|) ';\n\n const predicate = function (this: typeof sourceValues, value: number) {\n return value === this.b;\n };\n const result = e1.pipe(findIndex(predicate, sourceValues));\n\n expectObservable(result).toBe(expected, { x: 1 });\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n })" }, { @@ -49615,14 +49615,14 @@ "title": "should map using a custom thisArg" }, "behavioralClaim": "should map using a custom thisArg", - "classification": "portable", + "classification": "intentional-divergence", "disposition": "active", "modes": [ "cold", "polyfill", "native" ], - "reason": "Mechanically migrated and verified against the ColdObservable mode.", + "reason": "Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec binds the projector explicitly.", "duplicateOf": null, "imports": [ { @@ -49644,7 +49644,7 @@ "expectSubscriptions" ], "reviewFlags": [], - "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, expect, map } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n const addDrama = (x) => x + '!';\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot('-5-^-4--3---2----1--|');\n const e1subs = ' ^----------------!';\n const expected = ' --a--b---c----d--|';\n const values = { a: 46, b: 55, c: 64, d: 73 };\n const foo = {\n value: 42,\n };\n const result = applyOperators(e1, [map(function (x, index) {\n expect(this).to.equal(foo);\n return parseInt(x) + foo.value + index * 10;\n }, foo)]);\n expectObservable(result).toBe(expected, values);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", + "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, expect, map } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n const addDrama = (x) => x + '!';\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot('-5-^-4--3---2----1--|');\n const e1subs = ' ^----------------!';\n const expected = ' --a--b---c----d--|';\n const values = { a: 46, b: 55, c: 64, d: 73 };\n const foo = {\n value: 42,\n };\n const result = applyOperators(e1, [map(function (x, index) {\n expect(this).to.equal(foo);\n return parseInt(x) + foo.value + index * 10;\n }.bind(foo))]);\n expectObservable(result).toBe(expected, values);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", "originalSource": "it('should map using a custom thisArg', () => {\n testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {\n const e1 = hot('-5-^-4--3---2----1--|');\n const e1subs = ' ^----------------!';\n const expected = ' --a--b---c----d--|';\n const values = { a: 46, b: 55, c: 64, d: 73 };\n\n const foo = {\n value: 42,\n };\n const result = e1.pipe(\n map(function (this: typeof foo, x: string, index: number) {\n expect(this).to.equal(foo);\n return parseInt(x) + foo.value + index * 10;\n }, foo)\n );\n\n expectObservable(result).toBe(expected, values);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n })" }, { @@ -49711,14 +49711,14 @@ "title": "should do multiple maps using a custom thisArg" }, "behavioralClaim": "should do multiple maps using a custom thisArg", - "classification": "portable", + "classification": "intentional-divergence", "disposition": "active", "modes": [ "cold", "polyfill", "native" ], - "reason": "Mechanically migrated and verified against the ColdObservable mode.", + "reason": "Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec uses closed-over projectors.", "duplicateOf": null, "imports": [ { @@ -49734,7 +49734,7 @@ "expectSubscriptions" ], "reviewFlags": [], - "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, map } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n const addDrama = (x) => x + '!';\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot(' --1--2--3--4--|');\n const e1subs = ' ^-------------!';\n const expected = '--a--b--c--d--|';\n const values = { a: 11, b: 14, c: 17, d: 20 };\n class Filterer {\n selector1 = (x) => parseInt(x) + 2;\n selector2 = (x) => parseInt(x) * 3;\n }\n const filterer = new Filterer();\n const result = applyOperators(e1, [map(function (x) {\n return this.selector1(x);\n }, filterer),\n map(function (x) {\n return this.selector2(x);\n }, filterer),\n map(function (x) {\n return this.selector1(x);\n }, filterer)]);\n expectObservable(result).toBe(expected, values);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", + "migratedProgram": "async function migrated(runtime) {\nconst { rxTest, applyOperators, map } = runtime;\n const __subscriptionFrame = (marbles, marker, parseTime) => {\n const markerIndex = marbles.indexOf(marker);\n if (markerIndex < 0)\n return Infinity;\n const prefix = marbles.slice(0, markerIndex).replace(/[!^]/g, '-');\n return parseTime(prefix + '|');\n };\n const addDrama = (x) => x + '!';\n await rxTest(async ({ hot, expectObservable, expectSubscriptions, cold: __rxCold, hot: __rxHot, time: __rxTime, flush: __rxFlush, now: __rxNow, schedule: __rxSchedule }) => {\n const e1 = hot(' --1--2--3--4--|');\n const e1subs = ' ^-------------!';\n const expected = '--a--b--c--d--|';\n const values = { a: 11, b: 14, c: 17, d: 20 };\n class Filterer {\n selector1 = (x) => parseInt(x) + 2;\n selector2 = (x) => parseInt(x) * 3;\n }\n const filterer = new Filterer();\n const result = applyOperators(e1, [map(function (x) {\n return this.selector1(x);\n }.bind(filterer)),\n map(function (x) {\n return this.selector2(x);\n }.bind(filterer)),\n map(function (x) {\n return this.selector1(x);\n }.bind(filterer))]);\n expectObservable(result).toBe(expected, values);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n}\n", "originalSource": "it('should do multiple maps using a custom thisArg', () => {\n testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {\n const e1 = hot(' --1--2--3--4--|');\n const e1subs = ' ^-------------!';\n const expected = '--a--b--c--d--|';\n const values = { a: 11, b: 14, c: 17, d: 20 };\n\n class Filterer {\n selector1 = (x: string) => parseInt(x) + 2;\n selector2 = (x: string) => parseInt(x) * 3;\n }\n const filterer = new Filterer();\n\n const result = e1.pipe(\n map(function (this: any, x) {\n return this.selector1(x);\n }, filterer),\n map(function (this: any, x) {\n return this.selector2(x);\n }, filterer),\n map(function (this: any, x) {\n return this.selector1(x);\n }, filterer)\n );\n\n expectObservable(result).toBe(expected, values);\n expectSubscriptions(e1.subscriptions).toBe(e1subs);\n });\n })" }, { diff --git a/packages/rxjs/test/ported/migration-evidence-ledger.generated.json b/packages/rxjs/test/ported/migration-evidence-ledger.generated.json index 5afff51664..3087ac2731 100644 --- a/packages/rxjs/test/ported/migration-evidence-ledger.generated.json +++ b/packages/rxjs/test/ported/migration-evidence-ledger.generated.json @@ -1872,6 +1872,7 @@ "localEvidence": [], "testClassifications": [ "harness-rewrite", + "intentional-divergence", "portable" ], "modeResults": { @@ -1892,7 +1893,7 @@ "next": { "surface": "static-symbol", "module": "partition", - "mapping": "Observable[partition](source, predicate, thisArg?)", + "mapping": "Observable[partition](source, predicate)", "status": "Present; parity unverified", "sharingModel": "shared-active-platform", "cancellationModel": "AbortSignal", @@ -4592,7 +4593,7 @@ "next": { "surface": "instance-symbol", "module": "every", - "mapping": "source[every](predicate, thisArg?)", + "mapping": "source[every](predicate)", "status": "Present; parity unverified", "sharingModel": "shared-active-platform", "cancellationModel": "AbortSignal", @@ -4938,6 +4939,7 @@ ], "localEvidence": [], "testClassifications": [ + "intentional-divergence", "portable" ], "modeResults": { @@ -4958,7 +4960,7 @@ "next": { "surface": "instance-symbol", "module": "filter", - "mapping": "source[filter](predicate, thisArg?)", + "mapping": "source[filter](predicate)", "status": "Present; parity unverified", "sharingModel": "shared-active-platform", "cancellationModel": "AbortSignal", @@ -4968,7 +4970,7 @@ "action": "mechanical-change", "semanticReviewRequired": true, "adapter": "identity", - "note": "The RxJS Symbol form preserves predicate index, thisArg, type guards, errors, and cancellation without replacing the platform string method." + "note": "The RxJS Symbol form preserves predicate index, type guards, errors, and cancellation without replacing the platform string method; callback receivers must use closures or Function.prototype.bind." }, "decisions": [ "D-003", @@ -5069,6 +5071,7 @@ ], "localEvidence": [], "testClassifications": [ + "intentional-divergence", "portable" ], "modeResults": { @@ -5089,7 +5092,7 @@ "next": { "surface": "instance-symbol", "module": "find", - "mapping": "source[find](predicate, thisArg?)", + "mapping": "source[find](predicate)", "status": "Present; parity unverified", "sharingModel": "shared-active-platform", "cancellationModel": "AbortSignal", @@ -5099,7 +5102,7 @@ "action": "mechanical-change", "semanticReviewRequired": true, "adapter": "identity", - "note": "Emits the first matching value or undefined with predicate index, source identity, thisArg, early cancellation, and no replacement of the platform find() Promise consumer." + "note": "Emits the first matching value or undefined with predicate index, source identity, and early cancellation, without replacing the platform find() Promise consumer; callback receivers must use closures or Function.prototype.bind." }, "decisions": [ "D-003", @@ -5136,6 +5139,7 @@ ], "localEvidence": [], "testClassifications": [ + "intentional-divergence", "portable" ], "modeResults": { @@ -5156,7 +5160,7 @@ "next": { "surface": "instance-symbol", "module": "find-index", - "mapping": "source[findIndex](predicate, thisArg?)", + "mapping": "source[findIndex](predicate)", "status": "Present; parity unverified", "sharingModel": "shared-active-platform", "cancellationModel": "AbortSignal", @@ -5166,7 +5170,7 @@ "action": "mechanical-change", "semanticReviewRequired": true, "adapter": "identity", - "note": "Emits the first matching index or -1 with predicate source identity, thisArg, errors, and early cancellation." + "note": "Emits the first matching index or -1 with predicate source identity, errors, and early cancellation; callback receivers must use closures or Function.prototype.bind." }, "decisions": [ "D-003", @@ -6010,7 +6014,7 @@ "next": { "surface": "instance-symbol", "module": "map", - "mapping": "source[map](project, thisArg?)", + "mapping": "source[map](project)", "status": "Present; parity unverified", "sharingModel": "shared-active-platform", "cancellationModel": "AbortSignal", @@ -6020,7 +6024,7 @@ "action": "mechanical-change", "semanticReviewRequired": true, "adapter": "identity", - "note": "The RxJS Symbol form preserves projection index, thisArg, errors, and cancellation without replacing the platform string method." + "note": "The RxJS Symbol form preserves projection index, errors, and cancellation without replacing the platform string method; callback receivers must use closures or Function.prototype.bind." }, "decisions": [ "D-003", diff --git a/packages/rxjs/test/ported/platform/observables/partition.spec.ts b/packages/rxjs/test/ported/platform/observables/partition.spec.ts index c5967f7bdc..e6ac43e7b6 100644 --- a/packages/rxjs/test/ported/platform/observables/partition.spec.ts +++ b/packages/rxjs/test/ported/platform/observables/partition.spec.ts @@ -63,7 +63,7 @@ describe('partition (platform)', () => { expectSubscriptions(e1.subscriptions).toBe([e1subs, e1subs]); }); }); - it('should partition an observable into two using a predicate and thisArg', async () => { + it('should partition an observable into two using a bound predicate', async () => { await rxTest(({ hot, expectSubscriptions, expectObservable }) => { const e1 = hot(' --a-b---a------d--a---c--|'); const e1subs = ' ^------------------------!'; @@ -79,7 +79,7 @@ describe('partition (platform)', () => { for (let index_1 = 0; index_1 < result_1.length; index_1++) { expectObservable(result_1[index_1]).toBe(expected_1[index_1]); } - })(Observable[partition](e1, predicate, { value: 'a' }), expected); + })(Observable[partition](e1, predicate.bind({ value: 'a' })), expected); expectSubscriptions(e1.subscriptions).toBe([e1subs, e1subs]); }); }); diff --git a/packages/rxjs/test/ported/platform/operators/filter.spec.ts b/packages/rxjs/test/ported/platform/operators/filter.spec.ts index 976bfb2434..73af3b1faf 100644 --- a/packages/rxjs/test/ported/platform/operators/filter.spec.ts +++ b/packages/rxjs/test/ported/platform/operators/filter.spec.ts @@ -363,7 +363,7 @@ describe('filter (platform)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should be able to accept and use a thisArg', async () => { + it('should be able to use closed-over predicates', async () => { function oddFilter(x) { return +x % 2 === 1; } @@ -388,15 +388,7 @@ describe('filter (platform)', () => { filter2 = (x) => +x % 3 === 0; } const filterer = new Filterer(); - const result = e1[filter](function (x) { - return this.filter1(x); - }, filterer) - [filter](function (x) { - return this.filter2(x); - }, filterer) - [filter](function (x) { - return this.filter1(x); - }, filterer); + const result = e1[filter](filterer.filter1)[filter](filterer.filter2)[filter](filterer.filter1); expectObservable(result).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); diff --git a/packages/rxjs/test/ported/platform/operators/find-index.spec.ts b/packages/rxjs/test/ported/platform/operators/find-index.spec.ts index 85c62cd6e7..59ad954bda 100644 --- a/packages/rxjs/test/ported/platform/operators/find-index.spec.ts +++ b/packages/rxjs/test/ported/platform/operators/find-index.spec.ts @@ -77,7 +77,7 @@ describe('findIndex (platform)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should work with a custom thisArg', async () => { + it('should work with a bound predicate', async () => { function truePredicate(x) { return true; } @@ -89,7 +89,7 @@ describe('findIndex (platform)', () => { const predicate = function (value) { return value === this.b; }; - const result = e1[findIndex](predicate, sourceValues); + const result = e1[findIndex](predicate.bind(sourceValues)); expectObservable(result).toBe(expected, { x: 1 }); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); diff --git a/packages/rxjs/test/ported/platform/operators/find.spec.ts b/packages/rxjs/test/ported/platform/operators/find.spec.ts index 6c2c4a3df2..fc30f88b4d 100644 --- a/packages/rxjs/test/ported/platform/operators/find.spec.ts +++ b/packages/rxjs/test/ported/platform/operators/find.spec.ts @@ -77,7 +77,7 @@ describe('find (platform)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should work with a custom thisArg', async () => { + it('should work with a bound predicate', async () => { function truePredicate(x) { return true; } @@ -91,7 +91,7 @@ describe('find (platform)', () => { const predicate = function (value) { return value === this.target; }; - expectObservable(e1[find](predicate, finder)).toBe(expected); + expectObservable(e1[find](predicate.bind(finder))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); diff --git a/packages/rxjs/test/ported/platform/operators/map.spec.ts b/packages/rxjs/test/ported/platform/operators/map.spec.ts index 9b37ad8ea5..b7903bada0 100644 --- a/packages/rxjs/test/ported/platform/operators/map.spec.ts +++ b/packages/rxjs/test/ported/platform/operators/map.spec.ts @@ -169,7 +169,7 @@ describe('map (platform)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should map using a custom thisArg', async () => { + it('should map using a bound projector', async () => { const addDrama = (x) => x + '!'; await rxTest(({ hot, expectObservable, expectSubscriptions }) => { const e1 = hot('-5-^-4--3---2----1--|'); @@ -179,10 +179,12 @@ describe('map (platform)', () => { const foo = { value: 42, }; - const result = e1[map](function (x, index) { - expect(this).toBe(foo); - return parseInt(x) + foo.value + index * 10; - }, foo); + const result = e1[map]( + function (x, index) { + expect(this).toBe(foo); + return parseInt(x) + foo.value + index * 10; + }.bind(foo) + ); expectObservable(result).toBe(expected, values); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); @@ -214,7 +216,7 @@ describe('map (platform)', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); }); - it('should do multiple maps using a custom thisArg', async () => { + it('should do multiple maps using closed-over projectors', async () => { const addDrama = (x) => x + '!'; await rxTest(({ hot, expectObservable, expectSubscriptions }) => { const e1 = hot(' --1--2--3--4--|'); @@ -226,15 +228,7 @@ describe('map (platform)', () => { selector2 = (x) => parseInt(x) * 3; } const filterer = new Filterer(); - const result = e1[map](function (x) { - return this.selector1(x); - }, filterer) - [map](function (x) { - return this.selector2(x); - }, filterer) - [map](function (x) { - return this.selector1(x); - }, filterer); + const result = e1[map](filterer.selector1)[map](filterer.selector2)[map](filterer.selector1); expectObservable(result).toBe(expected, values); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); diff --git a/packages/rxjs/test/ported/tools/generate-port-manifest.mjs b/packages/rxjs/test/ported/tools/generate-port-manifest.mjs index d0899bf98e..7c2e10cf8d 100644 --- a/packages/rxjs/test/ported/tools/generate-port-manifest.mjs +++ b/packages/rxjs/test/ported/tools/generate-port-manifest.mjs @@ -662,6 +662,30 @@ const harnessRewritesReplacingUnavailableImports = new Set([ 'spec/observables/from-spec.ts:21:from > should create an observable from an array', ]); const intentionalDivergenceReasons = new Map([ + [ + 'spec/observables/partition-spec.ts:77:partition > should partition an observable into two using a predicate and thisArg', + 'Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec binds the predicate explicitly.', + ], + [ + 'spec/operators/filter-spec.ts:235:filter > should be able to accept and use a thisArg', + 'Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec uses closed-over predicates.', + ], + [ + 'spec/operators/find-spec.ts:89:find > should work with a custom thisArg', + 'Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec binds the predicate explicitly.', + ], + [ + 'spec/operators/findIndex-spec.ts:89:findIndex > should work with a custom thisArg', + 'Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec binds the predicate explicitly.', + ], + [ + 'spec/operators/map-spec.ts:216:map > should map using a custom thisArg', + 'Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec binds the projector explicitly.', + ], + [ + 'spec/operators/map-spec.ts:269:map > should do multiple maps using a custom thisArg', + 'Intentional RxJS Next divergence: the callback receiver argument is removed; the active migrated spec uses closed-over projectors.', + ], [ 'spec/operators/delay-spec.ts:227:delay > should unsubscribe scheduled actions after execution', 'Intentional RxJS Next divergence: the executable replacement asserts AbortSignal cancellation and virtual host-timer cleanup instead of removed RxJS 7 source and Subscription._finalizers internals.', @@ -7033,7 +7057,7 @@ function buildMigratedProgram({ caseId, callback, imports, sourceFile, support, ? `await rxTest(async (${manualContext}) => {\n${rewriteManualHelperCalls(callbackBody)}\n});` : callbackBody; const input = `async function migrated(runtime) {\n${helperPrelude}\n${supportSource}\n${migratedBody}\n}`; - return transpileMigratedProgram( + const program = transpileMigratedProgram( input, imports, [], @@ -7043,6 +7067,43 @@ function buildMigratedProgram({ caseId, callback, imports, sourceFile, support, modeAwareSubscriptionExpectations.get(caseId), expectedValueDictionaries.get(caseId) ); + return rewriteRemovedCallbackReceiver(caseId, program); +} + +function rewriteRemovedCallbackReceiver(caseId, program) { + switch (caseId) { + case 'spec/observables/partition-spec.ts:77:partition > should partition an observable into two using a predicate and thisArg': + return replaceRemovedCallbackReceiver( + caseId, + program, + "partition(e1, predicate, { value: 'a' })", + "partition(e1, predicate.bind({ value: 'a' }))" + ); + case 'spec/operators/filter-spec.ts:235:filter > should be able to accept and use a thisArg': + case 'spec/operators/map-spec.ts:269:map > should do multiple maps using a custom thisArg': + return replaceRemovedCallbackReceiver(caseId, program, '}, filterer)', '}.bind(filterer))', 3); + case 'spec/operators/find-spec.ts:89:find > should work with a custom thisArg': + return replaceRemovedCallbackReceiver(caseId, program, 'find(predicate, finder)', 'find(predicate.bind(finder))'); + case 'spec/operators/findIndex-spec.ts:89:findIndex > should work with a custom thisArg': + return replaceRemovedCallbackReceiver( + caseId, + program, + 'findIndex(predicate, sourceValues)', + 'findIndex(predicate.bind(sourceValues))' + ); + case 'spec/operators/map-spec.ts:216:map > should map using a custom thisArg': + return replaceRemovedCallbackReceiver(caseId, program, '}, foo)', '}.bind(foo))'); + default: + return program; + } +} + +function replaceRemovedCallbackReceiver(caseId, program, receiverArgument, boundCallback, expectedCount = 1) { + const actualCount = program.split(receiverArgument).length - 1; + if (actualCount !== expectedCount) { + throw new Error(`Expected ${expectedCount} callback receiver rewrite(s) for ${caseId}; found ${actualCount}.`); + } + return program.replaceAll(receiverArgument, boundCallback); } function getPortedSchedulerAdapterReason({ imports, source }) { diff --git a/packages/rxjs/test/types/usage.ts b/packages/rxjs/test/types/usage.ts index a4253c0094..02f16d68e0 100644 --- a/packages/rxjs/test/types/usage.ts +++ b/packages/rxjs/test/types/usage.ts @@ -1,5 +1,10 @@ import { ColdObservable, Subject, TimeoutError } from 'rxjs'; +import { every } from 'rxjs/every'; +import { filter } from 'rxjs/filter'; +import { find } from 'rxjs/find'; +import { findIndex } from 'rxjs/find-index'; import { map } from 'rxjs/map'; +import { partition } from 'rxjs/partition'; import { pipe } from 'rxjs/pipe'; import { scan } from 'rxjs/scan'; import { switchMap } from 'rxjs/switch-map'; @@ -18,6 +23,20 @@ const timerResult: Observable = Observable[timer](10); const piped: Observable = source[pipe]((value) => value[map]((item) => String(item))); const staticPiped: Observable = Observable[pipe]([1, 2], (value) => value[map]((item) => String(item))); +const callbackContext = {}; +// @ts-expect-error RxJS 9 callback APIs do not accept a thisArg. +source[every](() => true, callbackContext); +// @ts-expect-error RxJS 9 callback APIs do not accept a thisArg. +source[filter](() => true, callbackContext); +// @ts-expect-error RxJS 9 callback APIs do not accept a thisArg. +source[find](() => true, callbackContext); +// @ts-expect-error RxJS 9 callback APIs do not accept a thisArg. +source[findIndex](() => true, callbackContext); +// @ts-expect-error RxJS 9 callback APIs do not accept a thisArg. +source[map]((value) => value, callbackContext); +// @ts-expect-error RxJS 9 callback APIs do not accept a thisArg. +Observable[partition](source, () => true, callbackContext); + const cold = new ColdObservable((subscriber) => subscriber.next(1)); const subject = new Subject(); const timeoutError = new TimeoutError({ lastValue: 1, meta: undefined, seen: 1 });