fix VerseRef.chapterNum setter infinite recursion - #58
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) <[email protected]>
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:
|
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