fix VerseRef.chapterNum setter infinite recursion - #58
Merged
Merged
Conversation
The setter assigned to itself (`this.chapterNum = value`) instead of the
backing field, so any assignment recursed until `RangeError: Maximum call
stack size exceeded`. It was marked with a `ToDo` placeholder.
Ported from the C# `VerseRef.ChapterNum` setter, which guards against
negative values before assigning:
if (value < 0)
throw new VerseRefException("ChapterNum can not be negative");
chapterNum = (short)value;
Tests port `BuildVerseRefByProps` from SIL.Scripture.Tests, which builds a
VerseRef entirely through property setters and so covers this directly. One
assertion is commented out: chapter 0 is out of range in the C#, but
`internalValid()` cannot detect that until the versification port lands.
Also adds TS-only tests for the negative guard and the zero boundary, since
the C# has no direct test for the setter's validation.
The `-1` sentinel path is unaffected: `setEmpty()` and the `chapter` string
setter write the backing field directly, exactly as in the C#.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #58 +/- ##
==========================================
+ Coverage 81.40% 83.93% +2.53%
==========================================
Files 4 4
Lines 328 330 +2
Branches 76 77 +1
==========================================
+ Hits 267 277 +10
+ Misses 39 33 -6
+ Partials 22 20 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
irahopkinson
added a commit
that referenced
this pull request
Aug 10, 2026
The setter assigned the backing field and nothing else, marked with a `ToDo`
placeholder. It was missing both of the other things the C# `VerseRef.VerseNum`
setter does:
if (value < 0)
throw new VerseRefException("VerseNum can not be negative");
verseNum = (short)value;
verse = null;
So negative verse numbers were accepted, and assigning `verseNum` left the
range/segment string in place. Setting `verseNum = 9` on `LUK 3:4b-5a` left a
stale `4b-5a` in the `verse` getter and `hasMultiple` still `true`.
Uses `this._verse = undefined` rather than `null`, per the repo convention of
preferring `undefined` for missing values. The C# `(short)` cast is not
replicated.
The C# test that covers the range-clearing is `CopyVerseFrom`, which sets
`VerseNum = 9` on a `LUK 3:4b-6a` source and then asserts the copied `Verse` is
`"9"`. That test cannot be ported yet — `copyVerseFrom` is not implemented in
this port — so the cases are added as TS-only tests instead: the negative
guard, the zero boundary, and the clearing of both a range and a segment.
`BuildVerseRefByProps` already exercised `verseNum = 0/15/17` and still passes;
those are plain numbers with no verse string to clear.
Behaviour change for consumers: unlike the sibling `chapterNum` setter fixed in
#58, this setter worked before, so code reading `verse`/`hasMultiple` after
assigning `verseNum` will see different results.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
irahopkinson
added a commit
that referenced
this pull request
Aug 10, 2026
The setter assigned the backing field and nothing else, marked with a `ToDo`
placeholder. It was missing both of the other things the C# `VerseRef.VerseNum`
setter does:
if (value < 0)
throw new VerseRefException("VerseNum can not be negative");
verseNum = (short)value;
verse = null;
So negative verse numbers were accepted, and assigning `verseNum` left the
range/segment string in place. Setting `verseNum = 9` on `LUK 3:4b-5a` left a
stale `4b-5a` in the `verse` getter and `hasMultiple` still `true`.
Uses `this._verse = undefined` rather than `null`, per the repo convention of
preferring `undefined` for missing values. The C# `(short)` cast is not
replicated.
The C# test that covers the range-clearing is `CopyVerseFrom`, which sets
`VerseNum = 9` on a `LUK 3:4b-6a` source and then asserts the copied `Verse` is
`"9"`. That test cannot be ported yet — `copyVerseFrom` is not implemented in
this port — so the cases are added as TS-only tests instead: the negative
guard, the zero boundary, and the clearing of both a range and a segment.
`BuildVerseRefByProps` already exercised `verseNum = 0/15/17` and still passes;
those are plain numbers with no verse string to clear.
Behaviour change for consumers: unlike the sibling `chapterNum` setter fixed in
#58, this setter worked before, so code reading `verse`/`hasMultiple` after
assigning `verseNum` will see different results.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
irahopkinson
added a commit
to eten-tech-foundation/scripture-editors
that referenced
this pull request
Aug 25, 2026
Routine minor bump, not a security update: the advisory-ID set is unchanged (GHSA-g2r8-wvmj-jf5w, GHSA-vp3h-ghgh-jr7g before and after), so this neither closes nor opens a finding. No overrides needed. Skips 2.0.6, so it also picks up the `VerseRef.chapterNum` setter infinite-recursion fix (sillsdev/scripture#58). 2.1.0 is flagged upstream as a potential breaking change: the `VerseRef.verseNum` setter was a mis-port that assigned the backing field and nothing else, and now also clears the verse string and throws on negative values, matching the C# `VerseRef.VerseNum` it ports. We are not affected. That is an instance setter on the `VerseRef` class, and outside the vendored `demos/platform/lib` copy this repo never touches the class — only `SerializedVerseRef`, an interface whose `verseNum` is a plain data property with no setter. The two `new VerseRef(...)` sites in platform-bible-utils' scripture-util.ts are read-only expressions that never store the instance, and every `verseNum`/`chapterNum` assignment in the tree targets a plain object or a local number. The upstream release notes survey this repo by name and reach the same conclusion. 2.1.0 declares no dependencies, so the lockfile diff is confined to the three specifiers and the single package entry. Verified with `nx run-many -t build test lint typecheck --skip-nx-cache` across all 10 projects: green, 1793 tests passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
irahopkinson
added a commit
to eten-tech-foundation/scripture-editors
that referenced
this pull request
Aug 25, 2026
Routine minor bump, not a security update: the advisory-ID set is unchanged (GHSA-g2r8-wvmj-jf5w, GHSA-vp3h-ghgh-jr7g before and after), so this neither closes nor opens a finding. No overrides needed. Skipping 2.0.6 means this picks up two `VerseRef` setter repairs at once. Neither changes our behaviour today — we hold no `VerseRef` instance to assign to — but both were armed traps that would have fired the first time we reached for that API, so taking the bump disarms them ahead of the need rather than after it. `chapterNum` (2.0.6, sillsdev/scripture#58) assigned the property to itself behind a `ToDo` placeholder and recursed infinitely. There was no guard in front of it, so every input threw `RangeError: Maximum call stack size exceeded` — `= 5`, `= 0`, `= -1` alike. The property was completely unusable. `verseNum` (2.1.0, sillsdev/scripture#60) is the same class of mis-port, and is flagged upstream as a potential breaking change because it did work. It assigned the backing field and nothing else, where the C# it ports also clears the verse string and rejects negatives. So `new VerseRef('LUK','3','4b-5a')` then `verseNum = 9` left `verse` reading '4b-5a' and `hasMultiple` true, describing the old reference while `verseNum` described the new one. Of the two, `verseNum` was the more dangerous to us: the recursion fails loud on first use, while the stale string fails silent and flows onward into anything reading `verse` or `toString()`. We reach neither today. Both are instance setters on the `VerseRef` class, and outside the vendored `demos/platform/lib` copy this repo never touches the class — only `SerializedVerseRef`, an interface whose `verseNum` is a plain data property with no setter. The two `new VerseRef(...)` sites in platform-bible-utils' scripture-util.ts are read-only expressions that never store the instance, and every `verseNum`/`chapterNum` assignment in the tree targets a plain object or a local number. The upstream release notes survey this repo by name and reach the same conclusion. 2.1.0 declares no dependencies, so the lockfile diff is confined to the three specifiers and the single package entry. Verified with `nx run-many -t build test lint typecheck --skip-nx-cache` across all 10 projects: green, 1793 tests passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 25, 2026
irahopkinson
added a commit
to eten-tech-foundation/scripture-editors
that referenced
this pull request
Aug 27, 2026
Routine minor bump, not a security update: the advisory-ID set is unchanged (GHSA-g2r8-wvmj-jf5w, GHSA-vp3h-ghgh-jr7g before and after), so this neither closes nor opens a finding. No overrides needed. Skipping 2.0.6 means this picks up two `VerseRef` setter repairs at once. Neither changes our behaviour today — we hold no `VerseRef` instance to assign to — but both were armed traps that would have fired the first time we reached for that API, so taking the bump disarms them ahead of the need rather than after it. `chapterNum` (2.0.6, sillsdev/scripture#58) assigned the property to itself behind a `ToDo` placeholder and recursed infinitely. There was no guard in front of it, so every input threw `RangeError: Maximum call stack size exceeded` — `= 5`, `= 0`, `= -1` alike. The property was completely unusable. `verseNum` (2.1.0, sillsdev/scripture#60) is the same class of mis-port, and is flagged upstream as a potential breaking change because it did work. It assigned the backing field and nothing else, where the C# it ports also clears the verse string and rejects negatives. So `new VerseRef('LUK','3','4b-5a')` then `verseNum = 9` left `verse` reading '4b-5a' and `hasMultiple` true, describing the old reference while `verseNum` described the new one. Of the two, `verseNum` was the more dangerous to us: the recursion fails loud on first use, while the stale string fails silent and flows onward into anything reading `verse` or `toString()`. We reach neither today. Both are instance setters on the `VerseRef` class, and outside the vendored `demos/platform/lib` copy this repo never touches the class — only `SerializedVerseRef`, an interface whose `verseNum` is a plain data property with no setter. The two `new VerseRef(...)` sites in platform-bible-utils' scripture-util.ts are read-only expressions that never store the instance, and every `verseNum`/`chapterNum` assignment in the tree targets a plain object or a local number. The upstream release notes survey this repo by name and reach the same conclusion. 2.1.0 declares no dependencies, so the lockfile diff is confined to the three specifiers and the single package entry. Verified with `nx run-many -t build test lint typecheck --skip-nx-cache` across all 10 projects: green, 1793 tests passed. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
irahopkinson
added a commit
to paranext/paranext-core
that referenced
this pull request
Sep 7, 2026
Bumps all 12 manifests that declare it: the root, `extensions`, and the 10 extensions under `extensions/src/*`. Skipping 2.0.6 means this picks up two `VerseRef` setter repairs at once. Neither changes our behaviour today, but both were armed traps, and this repo sits closer to them than any other paranext consumer. `chapterNum` (2.0.6, sillsdev/scripture#58) assigned the property to itself behind a `ToDo` placeholder and recursed infinitely. There was no guard in front of it, so every input threw `RangeError: Maximum call stack size exceeded` — `= 5`, `= 0`, `= -1` alike. The property was completely unusable, and nothing inside the library used it, so it was reachable only by consumers like us. `verseNum` (2.1.0, sillsdev/scripture#60) is the same class of mis-port, and is flagged upstream as a potential breaking change because this one did work. It assigned the backing field and nothing else, where the C# it ports also clears the verse string and rejects negatives. So `new VerseRef('LUK','3','4b-5a')` then `verseNum = 9` left `verse` reading '4b-5a' and `hasMultiple` true, describing the old reference while `verseNum` described the new one. Of the two, `verseNum` was the more dangerous: the recursion fails loud on first use, while the stale string fails silent and flows onward into anything reading `verse` or `toString()`. Why we are not affected today, and why it is closer here than elsewhere. Both are instance setters on the `VerseRef` class. Unlike the extension templates, this repo does hold real `VerseRef` instances — `const { verseRef } = VerseRef.tryParse(...)` in legacy-comment-manager's comment-list-scroll.utils.ts (48, 81) and comment-list.web-view.tsx (571). All three only ever read from them (`verseRef.bookNum` and similar); none assigns. The remaining instantiations, quick-verse/src/main.ts:191 and scripture-util.ts (325, 336), are read-only expressions that never store the instance. Every `chapterNum`/`verseNum` assignment in the repo targets a plain `SerializedVerseRef` — an interface with ordinary data properties and no setter — and each was checked individually rather than by name: - platform-scripture-editor.utils.ts:230 assigns to `let verseRef = { ...baseVerseRef }`, a spread of a `SerializedVerseRef` parameter, and the function's return type declares it as such. - platform-scripture-editor.utils.ts:332 assigns to `startVerseRef`, declared `SerializedVerseRef = { book: '', chapterNum: 0, verseNum: -1 }`. Worth noting that -1 initialiser: had this been an instance, the new negative guard would now throw. It is an object literal, so no setter runs. - checks-side-panel.web-view.tsx (168, 169) and inventory.web-view.tsx (97-104) assign to spreads of `SerializedVerseRef` literals. - usj-reader-writer.ts (3085-3162) assigns to `position`, an inline structural parameter type `{ bookId: string; chapterNum: number; verseNum: number }`, not a VerseRef. - usj-reader-writer.ts:1128 sets `lastVerseRef.verse`, declared `SerializedVerseRef | undefined` and built from an object literal at 1075. The `verse` string setter is untouched by either fix regardless. So the instances are sitting in variables one assignment away, which is the case for taking the fixes now rather than after someone writes that line. The lockfile diff is confined to `@sillsdev/scripture`; no file-link churn, since this repo is the source of those links rather than a consumer. npm also added `resolved` and `integrity` to the `node_modules/@sillsdev/scripture` entry, which previously carried neither, so the tarball is now pinned. Verified: `npm run typecheck`, `npm run lint`, and `npm test` all exit 0 across every workspace. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
irahopkinson
added a commit
to paranext/paranext-core
that referenced
this pull request
Sep 8, 2026
Bumps all 12 manifests that declare it: the root, `extensions`, and the 10 extensions under `extensions/src/*`. Only the root manifest changes what actually executes — `@sillsdev/scripture` is a webpack `external` for every extension and the host injects the runtime module, so the other eleven declarations govern types and Vitest resolution. Keeping them in lockstep is deliberate. Skipping 2.0.6 picks up two `VerseRef` setter repairs at once. The `chapterNum` setter (sillsdev/scripture#58) assigned the property to itself behind a `ToDo` placeholder and recursed infinitely, so every input threw `RangeError: Maximum call stack size exceeded` and the property was unusable. The `verseNum` setter (#60) is the same class of mis-port but did work: it assigned the backing field and nothing else, where the C# it ports also clears the verse string and rejects negatives, leaving `new VerseRef('LUK','3','4b-5a')` with `verse` reading '4b-5a' after `verseNum = 9`. Neither repair is purely an improvement. For non-negative invalid input `chapterNum` moves from unusable-but-loud to usable-but-silently -permissive: `chapterNum = 0` now yields `LUK 0:4` reporting valid, because `internalValid()` cannot detect it while the versification port is unfinished — the library's own new test has that assertion commented out behind a TODO. The reason to take this now is a live contract split with the C# half, not a hypothetical future assignment. `VerseRefConverter`'s reader resolves `verse ?? verseNum!.Value.ToString()`, so a present `verse` string wins over `verseNum`. That is exactly the object shape 2.0.5's setter produced: C# writes `{chapterNum:3, verseNum:4, verse:'4b-5a'}`, TypeScript sets `verseNum = 9` leaving the stale string, and C# reads it back as 4b-5a rather than 9 — silently, across the PAPI wire. The C# side already exercises these setters in production: `InputRange` writes `verseRef.VerseNum` and `verseRef.ChapterNum` on real `SIL.Scripture.VerseRef` instances. TypeScript was the odd one out. Nothing in this repo reaches the new guards today. The `VerseRef` constructor writes backing fields directly, and every instance site is read-only: `findScrollTarget` and `toNavigationRecord` in legacy-comment-manager's comment-list-scroll.utils.ts, `handleVerseRefClick` in its comment-list.web-view.tsx, `setHeresy` in quick-verse's main.ts, and `scrRefToBBBCCC`/`scrRefToBBBCCCVVV` in platform-bible-utils' scripture-util.ts. Every assignment to a `chapterNum`, `verseNum`, `verse` or `book` property in the repo targets a plain `SerializedVerseRef` or a structural object type, never a class instance. Cited by symbol rather than line, since these anchors have already drifted once across a rebase: - `determineLocationProperties` and `convertScriptureRangeToEditorRange` in platform-scripture-editor.utils.ts — the former assigns to a spread of a `SerializedVerseRef` parameter, the latter to a `SerializedVerseRef` declared with a `verseNum: -1` initialiser. That sentinel would throw under the new negative guard had it been an instance. - `checkInputRange` in checks-side-panel.web-view.tsx and `createInventoryInputRange` in inventory.web-view.tsx — spreads of `SerializedVerseRef` literals. - `transferFragmentsInfoArrayToMaps` in usj-reader-writer.ts — assigns to an inline structural parameter type, not a `VerseRef`. - `getVerseRefForIndexInUsfm` in the same file — sets `verse` and `book` on a `SerializedVerseRef | undefined` built from a literal. - `normalizeStoredScrRefs` in scroll-group.service-host.ts — sets `book`, which neither repair touches, behind a `bookNum` guard. The lockfile diff is confined to `@sillsdev/scripture`; no file-link churn, since this repo is the source of those links rather than a consumer. npm also added `resolved` and `integrity` to the `node_modules/@sillsdev/scripture` entry, which previously carried neither. Verified: `npm run typecheck`, `npm run lint`, `npm test` and `dotnet test c-sharp-tests/` all exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
irahopkinson
added a commit
to paranext/paranext-core
that referenced
this pull request
Sep 8, 2026
Bumps all 12 manifests that declare it: the root, `extensions`, and the 10 extensions under `extensions/src/*`. Only the root manifest changes what actually executes — `@sillsdev/scripture` is a webpack `external` for every extension and the host injects the runtime module, so the other eleven declarations govern types and Vitest resolution. Keeping them in lockstep is deliberate. Skipping 2.0.6 picks up two `VerseRef` setter repairs at once. The `chapterNum` setter (sillsdev/scripture#58) assigned the property to itself behind a `ToDo` placeholder and recursed infinitely, so every input threw `RangeError: Maximum call stack size exceeded` and the property was unusable. The `verseNum` setter (#60) is the same class of mis-port but did work: it assigned the backing field and nothing else, where the C# it ports also clears the verse string and rejects negatives, leaving `new VerseRef('LUK','3','4b-5a')` with `verse` reading '4b-5a' after `verseNum = 9`. Neither repair is purely an improvement. For non-negative invalid input `chapterNum` moves from unusable-but-loud to usable-but-silently -permissive: `chapterNum = 0` now yields `LUK 0:4` reporting valid, because `internalValid()` cannot detect it while the versification port is unfinished — the library's own new test has that assertion commented out behind a TODO. The reason to take this now is a live contract split with the C# half, not a hypothetical future assignment. `VerseRefConverter`'s reader resolves `verse ?? verseNum!.Value.ToString()`, so a present `verse` string wins over `verseNum`. That is exactly the object shape 2.0.5's setter produced: C# writes `{chapterNum:3, verseNum:4, verse:'4b-5a'}`, TypeScript sets `verseNum = 9` leaving the stale string, and C# reads it back as 4b-5a rather than 9 — silently, across the PAPI wire. The C# side already exercises these setters in production: `InputRange` writes `verseRef.VerseNum` and `verseRef.ChapterNum` on real `SIL.Scripture.VerseRef` instances. TypeScript was the odd one out. Nothing in this repo reaches the new guards today. The `VerseRef` constructor writes backing fields directly, and every instance site is read-only: `findScrollTarget` and `toNavigationRecord` in legacy-comment-manager's comment-list-scroll.utils.ts, `handleVerseRefClick` in its comment-list.web-view.tsx, `setHeresy` in quick-verse's main.ts, and `scrRefToBBBCCC`/`scrRefToBBBCCCVVV` in platform-bible-utils' scripture-util.ts. Every assignment to a `chapterNum`, `verseNum`, `verse` or `book` property in the repo targets a plain `SerializedVerseRef` or a structural object type, never a class instance. Cited by symbol rather than line, since these anchors have already drifted once across a rebase: - `determineLocationProperties` and `convertScriptureRangeToEditorRange` in platform-scripture-editor.utils.ts — the former assigns to a spread of a `SerializedVerseRef` parameter, the latter to a `SerializedVerseRef` declared with a `verseNum: -1` initialiser. That sentinel would throw under the new negative guard had it been an instance. - `checkInputRange` in checks-side-panel.web-view.tsx and `createInventoryInputRange` in inventory.web-view.tsx — spreads of `SerializedVerseRef` literals. - `transferFragmentsInfoArrayToMaps` in usj-reader-writer.ts — assigns to an inline structural parameter type, not a `VerseRef`. - `getVerseRefForIndexInUsfm` in the same file — sets `verse` and `book` on a `SerializedVerseRef | undefined` built from a literal. - `normalizeStoredScrRefs` in scroll-group.service-host.ts — sets `book`, which neither repair touches, behind a `bookNum` guard. The lockfile diff is confined to `@sillsdev/scripture`; no file-link churn, since this repo is the source of those links rather than a consumer. npm also added `resolved` and `integrity` to the `node_modules/@sillsdev/scripture` entry, which previously carried neither. Verified: `npm run typecheck`, `npm run lint`, `npm test` and `dotnet test c-sharp-tests/` all exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
irahopkinson
added a commit
to paranext/paranext-core
that referenced
this pull request
Sep 9, 2026
Bumps all 12 manifests that declare it: the root, `extensions`, and the 10 extensions under `extensions/src/*`. Only the root manifest changes what actually executes — `@sillsdev/scripture` is a webpack `external` for every extension and the host injects the runtime module, so the other eleven declarations govern types and Vitest resolution. Keeping them in lockstep is deliberate. Skipping 2.0.6 picks up two `VerseRef` setter repairs at once. The `chapterNum` setter (sillsdev/scripture#58) assigned the property to itself behind a `ToDo` placeholder and recursed infinitely, so every input threw `RangeError: Maximum call stack size exceeded` and the property was unusable. The `verseNum` setter (#60) is the same class of mis-port but did work: it assigned the backing field and nothing else, where the C# it ports also clears the verse string and rejects negatives, leaving `new VerseRef('LUK','3','4b-5a')` with `verse` reading '4b-5a' after `verseNum = 9`. Neither repair is purely an improvement. For non-negative invalid input `chapterNum` moves from unusable-but-loud to usable-but-silently -permissive: `chapterNum = 0` now yields `LUK 0:4` reporting valid, because `internalValid()` cannot detect it while the versification port is unfinished — the library's own new test has that assertion commented out behind a TODO. The reason to take this now is a live contract split with the C# half, not a hypothetical future assignment. `VerseRefConverter`'s reader resolves `verse ?? verseNum!.Value.ToString()`, so a present `verse` string wins over `verseNum`. That is exactly the object shape 2.0.5's setter produced: C# writes `{chapterNum:3, verseNum:4, verse:'4b-5a'}`, TypeScript sets `verseNum = 9` leaving the stale string, and C# reads it back as 4b-5a rather than 9 — silently, across the PAPI wire. The C# side already exercises these setters in production: `InputRange` writes `verseRef.VerseNum` and `verseRef.ChapterNum` on real `SIL.Scripture.VerseRef` instances. TypeScript was the odd one out. Nothing in this repo reaches the new guards today. The `VerseRef` constructor writes backing fields directly, and every instance site is read-only: `findScrollTarget` and `toNavigationRecord` in legacy-comment-manager's comment-list-scroll.utils.ts, `handleVerseRefClick` in its comment-list.web-view.tsx, `setHeresy` in quick-verse's main.ts, and `scrRefToBBBCCC`/`scrRefToBBBCCCVVV` in platform-bible-utils' scripture-util.ts. Every assignment to a `chapterNum`, `verseNum`, `verse` or `book` property in the repo targets a plain `SerializedVerseRef` or a structural object type, never a class instance. Cited by symbol rather than line, since these anchors have already drifted once across a rebase: - `determineLocationProperties` and `convertScriptureRangeToEditorRange` in platform-scripture-editor.utils.ts — the former assigns to a spread of a `SerializedVerseRef` parameter, the latter to a `SerializedVerseRef` declared with a `verseNum: -1` initialiser. That sentinel would throw under the new negative guard had it been an instance. - `checkInputRange` in checks-side-panel.web-view.tsx and `createInventoryInputRange` in inventory.web-view.tsx — spreads of `SerializedVerseRef` literals. - `transferFragmentsInfoArrayToMaps` in usj-reader-writer.ts — assigns to an inline structural parameter type, not a `VerseRef`. - `getVerseRefForIndexInUsfm` in the same file — sets `verse` and `book` on a `SerializedVerseRef | undefined` built from a literal. - `normalizeStoredScrRefs` in scroll-group.service-host.ts — sets `book`, which neither repair touches, behind a `bookNum` guard. The lockfile diff is confined to `@sillsdev/scripture`; no file-link churn, since this repo is the source of those links rather than a consumer. npm also added `resolved` and `integrity` to the `node_modules/@sillsdev/scripture` entry, which previously carried neither. Verified: `npm run typecheck`, `npm run lint`, `npm test` and `dotnet test c-sharp-tests/` all exit 0. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the
VerseRef.chapterNumsetter, which recursed infinitely on every assignment. Built test-first: the ported C# test was confirmed failing withRangeErrorbefore the fix went in.The bug
src/verse-ref.tsassigned the property to itself instead of the backing field, behind aToDoplaceholder:There's no guard in front of it, so every input threw
RangeError: Maximum call stack size exceeded—= 5,= 0,= -1alike. The property was completely unusable.Nothing inside the library was affected: the numeric constructor writes
_chapterNumdirectly (verse-ref.ts:244), andsetEmpty()and thechapterstring setter do the same. That line was the only write through the setter, so this was reachable only by external consumers.The fix
Ported from the C#
VerseRef.ChapterNum, which guards negatives then assigns:giving:
This mirrors the existing
bookNumsetter, which already ports its C# counterpart's throw verbatim. The(short)cast is deliberately not replicated — TS has noshort, and emulating 16-bit wraparound would be fidelity at the cost of sense.The
-1sentinel path is untouched.setEmpty()andset chapter('')write_chapterNumdirectly, so the new guard never intercepts them — exactly as in the C#, whereChaptersets the field to-1without going throughChapterNum.Chapter and Verse as Empty Stringsstill passes.Tests
Ports
BuildVerseRefByPropsfromSIL.Scripture.Tests, which builds aVerseRefentirely through property setters and so covers this path directly.One assertion is commented out. After
bookNum = 13, the C# expectsOutOfRangebecause chapter 0 is invalid, butinternalValid()has its chapter/verse range check commented out pending the versification port (see theTODOatverse-ref.ts:623), so it returnsValid. Commented rather than weakened, following the existing convention in this file, and it should be restored when that port lands. The other fivevalidStatus/validassertions in the ported test pass legitimately.Also adds two TS-only tests under
Extra (TS-only tests)for the negative guard and the zero boundary, since the C# has no direct test for the setter's validation.Is this a behaviour change?
Technically yes, but not a breaking one. Since every prior assignment crashed, there is no code path that worked before and behaves differently now — this is a patch-level repair of a property that was 100% unusable. The public API surface is unchanged; the
.d.tsdeclarations forchapterNumare identical.The only way to have depended on the old behaviour was catching the stack overflow as control flow, and even that largely survives: negatives still throw, now as
VerseRefExceptionrather thanRangeError.Verification
npm run test:ci— 43 passed (was 40)npm run lint— cleannpm run prettier:ci— cleannpm run build— cleanRed was confirmed twice before the fix: first
RangeErrorfrom the recursion for the ported test, thenexpected function to throw an error, but it didn'tfor the negative guard.Follow-up
set verseNumis the same class of mis-port and still carries itsToDo. The C# is:The TS does a bare
this._verseNum = value— missing both the guard and theverse = null. Sonew VerseRef('LUK','3','4b-5a')thenverseNum = 9leaves a stale'4b-5a'in theversegetter. Deliberately left out of this PR: unlikechapterNum, that setter works today, so fixing it changes the observable output of working consumer code and deserves its own review.🤖 Generated with Claude Code