Align formula error codes with Google Sheets conventions#104
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThis PR centralizes and typesheet error handling by introducing an ordered ErrValues/ErrValue set and frozen ErrNode singletons, replacing hardcoded error literals across formula evaluators, functions, and worksheet model code; it expands supported error codes to include Changes
Sequence Diagram(s)sequenceDiagram
participant Caller
participant Evaluator
participant FunctionModule
participant ErrNode
participant Worksheet
Caller->>Evaluator: evaluate(formula)
Evaluator->>FunctionModule: dispatch(functionName, args)
FunctionModule-->>ErrNode: return ErrNode.NAME / ErrNode.NUM / ...
FunctionModule-->>Evaluator: EvalNode (err singleton)
Evaluator->>Worksheet: write cell value (uses ErrValue.* if needed)
Worksheet-->>Caller: final cell value (err or normal)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Verification: verify:selfResult: ✅ PASS in 118.6s
Verification: verify:integrationResult: ✅ PASS |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/design/formula.md`:
- Around line 181-188: Update the `#NULL!` entry in the error table to mark it
as reserved/unsupported until the evaluator can actually emit it: either remove
the active description or replace it with a brief note like "reserved — engine
does not currently emit this error"; also add a short sentence near the grammar
discussion clarifying that there is currently no range-intersection operator and
the evaluator cannot produce `#NULL!` (reference `#NULL!` and the
grammar/range-intersection mention to locate where to edit).
In `@packages/sheets/src/formula/formula.ts`:
- Around line 496-499: The new ErrNode union includes '#NAME?' but the evaluator
still throws for unknown function calls and returns '#ERROR!' for unresolved
identifiers; update the name-resolution failure paths so they emit an ErrNode
with v: '#NAME?' instead of throwing or returning '#ERROR!'. Specifically, in
the function-call evaluation path (the code that currently throws for unknown
functions) and in the identifier resolution path (the code that currently
returns '#ERROR!'), replace those outcomes with returning { t: 'err', v:
'#NAME?' } so ERROR.TYPE and other error-handling downstream see the correct
error kind.
In `@packages/sheets/src/formula/functions-math.ts`:
- Around line 1208-1209: The combinatorics functions must consistently emit
`#NUM`! for invalid n/k domain checks; update the error return in permutFunc and
permutationaFunc (and the other occurrence around the earlier check at the spot
referenced near 1239) so that when n<0 || k<0 || k>n they return { t: 'err', v:
'#NUM!' } instead of the current { t: 'err', v: '#VALUE!' }; locate and replace
those error-return sites inside permutFunc and permutationaFunc (and the similar
check noted at the 1239 location) so ERROR.TYPE behavior matches COMBIN/COMBINA.
- Around line 1388-1390: The DECIMAL implementation currently uses
parseInt(str.v, b) which accepts partial matches (e.g., "12Z" -> 12); update the
DECIMAL validation so the entire input string only contains valid digits for
base b before returning a number or, alternatively, compare the parsed value
back to a fully-parsed string to ensure no trailing invalid characters.
Specifically, in the DECIMAL logic around parseInt(str.v, b) (variables b and
str.v), check that str.v matches an allowed-character pattern for base b (or
re-serialize the parsed value and verify equality) and if any invalid character
is present return { t: 'err', v: '#VALUE!' } instead of accepting a partial
parse.
In `@packages/sheets/src/formula/functions-statistical.ts`:
- Around line 967-969: The exclusive variants are returning the wrong error type
for out-of-range quart/percent/significance inputs; update quartileexcFunc,
percentileexcFunc, and percentrankexcFunc so that the checks that currently
return '#VALUE!' (and the sig < 1 check in percentrankexcFunc) instead return
'#NUM!' to match the INC counterparts and ensure consistent ERROR.TYPE behavior
for invalid-range and significance values.
In `@packages/sheets/src/formula/functions-text.ts`:
- Around line 784-789: The CHAR implementation returns supplementary characters
via String.fromCodePoint but LEN, LEFT, RIGHT, MID, and CODE still operate on
UTF-16 code units, causing broken behavior for non-BMP characters; update these
functions to be code-point aware: change LEN to count code points (e.g., iterate
with for...of or use Array.from/ spread on the string), change LEFT/RIGHT/MID to
slice by code points rather than code units (build substrings by iterating code
points), and change CODE to return the full code point via
String.prototype.codePointAt(0) (and bounds-check appropriately). Locate and
modify the implementations of LEN, LEFT, RIGHT, MID, CODE and ensure they
interoperate with CHAR(String.fromCodePoint) so supplementary characters are
treated as single characters throughout.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b589cdbe-94e6-4310-a15d-868049cb422d
📒 Files selected for processing (7)
docs/design/formula.mdpackages/sheets/src/formula/formula.tspackages/sheets/src/formula/functions-financial.tspackages/sheets/src/formula/functions-math.tspackages/sheets/src/formula/functions-statistical.tspackages/sheets/src/formula/functions-text.tspackages/sheets/test/formula/formula.test.ts
Expand ErrNode from 5 to 8 codes (adds #NULL!, #NAME?, #NUM!), align
numeric domain violations with Google Sheets, introduce named ErrValue
accessors so ERROR.TYPE and every call site share a single source of
truth, and collapse repeated `{ t: 'err', v: ErrValue.X }` literals
into frozen `ErrNode.X` singletons.
Why:
- Numeric domain violations previously returned #VALUE!, disagreeing
with Google Sheets (#NUM! / #DIV/0!). Downstream code branching on
ERROR.TYPE codes now matches Google's behavior.
- The 1200+ raw '#…' literals across formula/* and model/worksheet/*
meant ERROR.TYPE had to hand-maintain a duplicated 1–8 code table.
Centralizing the ordering in ErrValues[] lets errValueCode() derive
the code from the array index, and lets call sites reference
ErrValue.NA / ErrValue.DIV0 / … instead of magic strings.
- After centralizing ErrValue, every error return site still rebuilt
a fresh `{ t: 'err', v: ErrValue.X }` wrapper — 1175 allocations
that all carry the same shape. Hoisting them to frozen ErrNode.X
singletons removes the allocation churn, shortens call sites, and
gives one place to document when each code is appropriate.
Changes:
- ErrNode.v: 5 → 8 codes ordered to match ERROR.TYPE (1=#NULL!, …,
8=#ERROR!). Domain-violation functions updated: SQRT, LN, LOG,
LOG base, SQRTPI, MOD, QUOTIENT, POWER, COMBIN/COMBINA,
FACT/FACTDOUBLE, BASE/DECIMAL, ASIN/ACOS/ACOSH/ATANH/ACOTH,
CSCH/COTH, MULTINOMIAL, QUARTILE, PERCENTILE, PERCENTRANK,
EFFECT/NOMINAL, CHAR.
- Add ErrValue named-accessor object, ErrValue type alias, and
errValueCode(v) helper in formula.ts. Replace literal '#…' with
ErrValue.* across 16 source files.
- Add ErrNode frozen-singleton const (ErrNode.NULL, DIV0, VALUE,
REF, NAME, NUM, NA, ERROR) in formula.ts with per-code JSDoc
explaining when each code should be emitted — pick-the-right-code
guidance lives with the singletons themselves. Replace 1175 call
sites of `{ t: 'err', v: ErrValue.X }` with `ErrNode.X` across
arguments.ts, functions-database/date/engineering/financial/
helpers/info/logical/lookup/math/statistical/text.ts, and
formula.ts itself. Drop unused ErrValue imports where the file
now only emits errors (never compares against them).
- ERROR.TYPE uses errValueCode() instead of a duplicated map and
now dereferences single-cell refs, so ERROR.TYPE(A1) where A1
holds an error string returns 1–8.
- Google Sheets parity fixes from review feedback:
• PERMUT / PERMUTATIONA domain errors now emit #NUM! (matching
COMBIN) instead of #VALUE!.
• QUARTILE.EXC / PERCENTILE.EXC / PERCENTRANK.EXC emit #NUM!
for out-of-domain quartile, k, or significance arguments.
• DECIMAL validates every character against the requested base
before calling parseInt, so DECIMAL(\"12Z\", 10) returns
#NUM! instead of silently truncating to 12; empty string
returns #VALUE!.
- defaultAlign in input.ts now checks membership in the shared
ErrValues list directly, dropping the hand-maintained partial
FormulaErrorValues set.
- docs/design/sheets/formula.md: sync ErrNode docs, expand the
Error Types table to all 8 codes, and note that #NULL! is
reserved only to preserve the canonical ERROR.TYPE ordering —
Wafflebase never emits it because the Excel space-intersection
operator does not exist in Google Sheets / Wafflebase.
- Tests: reference-based ERROR.TYPE cases (1–8 over error cells;
#N/A for number/text/boolean/empty/range), PERMUT/PERMUTATIONA
domain-error cases, DECIMAL invalid-character and empty-string
cases, and EXC-variant domain-error cases.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.7 <[email protected]>
a0a6ef4 to
29433de
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
packages/sheets/src/formula/functions-financial.ts (1)
1258-1780:⚠️ Potential issue | 🟡 MinorFinancial functions should return
#NUM!(not#VALUE!) for zero denominators and numeric-domain violations, consistent with Google Sheets behavior.The PR standardizes numeric errors to
#NUM!, but several financial functions still return#VALUE!for cases that should be#NUM!:
- DISC (line 1258), YIELDDISC (line 1313), RECEIVED (line 1429), INTRATE (line 1457), PRICEMAT (line 1600), YIELDMAT (line 1633/1635): Return
#VALUE!when divisors are zero; should returnErrNode.NUM.- RRI (line 1780): Returns
#VALUE!whenpv.v === 0ornper.v === 0; should returnErrNode.NUM.- PDURATION (line 1758): Returns
#VALUE!for negative/zero rates and values; should returnErrNode.NUM.- RATE (line 419), IRR (line 462/467), XIRR (line 793/798): Non-convergence paths return
#VALUE!; should returnErrNode.NUMper Google Sheets convention.Consider consolidating these into a follow-up PR for consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-financial.ts` around lines 1258 - 1780, Several financial functions return ErrNode.VALUE (i.e., `#VALUE`!) for zero denominators or numeric-domain violations but per PR guidance and Google Sheets behavior they should return ErrNode.NUM (`#NUM`!); update the error returns in DISC (discountFunc), YIELDDISC (yielddiscFunc), RECEIVED (receivedFunc), INTRATE (intrateFunc), PRICEMAT (pricematFunc), YIELDMAT (yieldmatFunc), PDURATION (pdurationFunc) and RRI (rriFunc) to return ErrNode.NUM where the code currently returns ErrNode.VALUE (or does numeric-domain checks), and change non-convergence/error paths in RATE, IRR, and XIRR implementations (functions named rateFunc, irrFunc, xirrFunc) to return ErrNode.NUM instead of ErrNode.VALUE; ensure you only replace the specific error return values and keep the existing guard checks and control flow intact.packages/sheets/src/formula/functions-engineering.ts (1)
352-367:⚠️ Potential issue | 🟠 MajorReturn
#NUM!for DEC2 numeric-domain failures.*These branches still map out-of-range inputs and too-small
placestoErrNode.VALUE, soERROR.TYPEremains inconsistent with the PR’s numeric-domain convention.🔧 Proposed error-code alignment
- if (val < -549755813888 || val > 549755813887) return ErrNode.VALUE; + if (val < -549755813888 || val > 549755813887) return ErrNode.NUM; @@ - if (p < hex.length) return ErrNode.VALUE; + if (p < hex.length) return ErrNode.NUM; @@ - if (val < -512 || val > 511) return ErrNode.VALUE; + if (val < -512 || val > 511) return ErrNode.NUM; @@ - if (p < bin.length) return ErrNode.VALUE; + if (p < bin.length) return ErrNode.NUM; @@ - if (val < -536870912 || val > 536870911) return ErrNode.VALUE; + if (val < -536870912 || val > 536870911) return ErrNode.NUM; @@ - if (p < oct.length) return ErrNode.VALUE; + if (p < oct.length) return ErrNode.NUM;Also applies to: 410-425, 468-483
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-engineering.ts` around lines 352 - 367, The numeric-domain error handling in functions-engineering.ts must return ErrNode.NUM (representing `#NUM`!) instead of ErrNode.VALUE for DEC2* conversions: change the out-of-range check that currently returns ErrNode.VALUE when val < -549755813888 || val > 549755813887 to return ErrNode.NUM, and change the places-too-small branch (the check if p < hex.length) that returns ErrNode.VALUE to return ErrNode.NUM; apply the same replacement in the other DEC2* blocks referenced (the similar branches around the blocks handling ranges and places at the locations corresponding to the other diffs, e.g. the blocks around lines 410-425 and 468-483) while leaving existing early error propagation (places.t === 'err') unchanged.packages/sheets/src/formula/functions-statistical.ts (2)
1266-1305:⚠️ Potential issue | 🟠 MajorGuard single-value
PERCENTRANKarrays before dividing byn - 1.With one numeric value and
xequal to that value,rank = smaller / (n - 1)becomes0 / 0, returning{ t: 'num', v: NaN }instead of a spreadsheet error.🛡️ Proposed guard
const vals = collectNumericValues(exprs[0], visit, grid); if (!Array.isArray(vals)) return vals; if (vals.length === 0) return ErrNode.NA; + if (vals.length < 2) return ErrNode.DIV0;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-statistical.ts` around lines 1266 - 1305, The PERCENTRANK implementation can divide by zero when vals has a single numeric value (n === 1); add a guard after computing n (and before any division by n - 1) to detect n === 1 and return a spreadsheet error (e.g. ErrNode.DIV0) so we don't compute rank = smaller / (n - 1) or do interpolation; update the block surrounding vars n, smaller, idx, and rank in functions-statistical.ts to return ErrNode.DIV0 when n === 1.
3106-3124:⚠️ Potential issue | 🟠 MajorAvoid
NaNfromZ.TESTwhen sigma is omitted for a single sample.For one data point without an explicit sigma, the sample variance divides by
dataResult.length - 1(0), producingNaNand returning it as a numeric result. Also reject non-positive explicit sigma before using it as a divisor.🛡️ Proposed guard
const dataResult = collectNumericValues(exprs[0], visit, grid); if (!Array.isArray(dataResult)) return dataResult; if (dataResult.length === 0) return ErrNode.VALUE; @@ if (exprs.length >= 3) { const s = NumberArgs.map(visit(exprs[2]), grid); if (s.t === 'err') return s; sigma = s.v; + if (sigma <= 0) return ErrNode.VALUE; } else { + if (dataResult.length < 2) return ErrNode.DIV0; // Sample standard deviation const variance = dataResult.reduce((a, b) => a + (b - mean) ** 2, 0) / (dataResult.length - 1); sigma = Math.sqrt(variance); + if (sigma === 0) return ErrNode.DIV0; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-statistical.ts` around lines 3106 - 3124, The Z.TEST implementation can produce NaN when sigma is omitted for a single sample and must reject non-positive explicit sigma; update the block in the function using collectNumericValues, exprs, NumberArgs.map and sigma so that if dataResult.length - 1 <= 0 (i.e. single data point and no explicit sigma) you return ErrNode.DIV0 (or the project's appropriate divide-by-zero error) instead of computing variance, and if an explicit sigma (from NumberArgs.map(visit(exprs[2]))) is provided validate that s.v is finite and > 0 and return ErrNode.DIV0 (or ErrNode.NUM per project convention) when not, before using sigma as a divisor; keep the rest of the z calculation and final return using normCdf unchanged.packages/sheets/src/formula/functions-math.ts (3)
2084-2120:⚠️ Potential issue | 🟠 Major
AGGREGATEstatistical error paths diverge fromSUBTOTALand Google Sheets.
SUBTOTALnow correctly returnsErrNode.DIV0when there aren't enough values forSTDEV/STDEVP/VAR/VARP(lines 2018, 2025, 2034, 2040), matching Google Sheets. The same cases insideAGGREGATE(func nums 7, 8, 10, 11) still returnErrNode.VALUE, so e.g.AGGREGATE(7, 0, A1)vs.SUBTOTAL(7, A1)on a single value will report differentERROR.TYPEcodes for the same underlying cause.Also, the
defaultbranch (line 2119) is hit for an unknownfunction_num; Google Sheets returns#VALUE!there, which is fine, but note that 12 (MEDIAN) is the only extra entry — if you intend to support 13–19 (MODE/LARGE/SMALL/PERCENTILE/QUARTILE/…) the fall-through is silently wrong.🛠️ Proposed fix for the insufficient-sample cases
case 7: { // STDEV.S - if (nums.length < 2) return ErrNode.VALUE; + if (nums.length < 2) return ErrNode.DIV0; const mean = nums.reduce((a, b) => a + b, 0) / nums.length; const variance = nums.reduce((a, b) => a + (b - mean) ** 2, 0) / (nums.length - 1); return { t: 'num', v: Math.sqrt(variance) }; } case 8: { // STDEV.P - if (nums.length === 0) return ErrNode.VALUE; + if (nums.length === 0) return ErrNode.DIV0; const mean = nums.reduce((a, b) => a + b, 0) / nums.length; const variance = nums.reduce((a, b) => a + (b - mean) ** 2, 0) / nums.length; return { t: 'num', v: Math.sqrt(variance) }; } case 9: return { t: 'num', v: nums.reduce((a, b) => a + b, 0) }; // SUM case 10: { // VAR.S - if (nums.length < 2) return ErrNode.VALUE; + if (nums.length < 2) return ErrNode.DIV0; const mean = nums.reduce((a, b) => a + b, 0) / nums.length; return { t: 'num', v: nums.reduce((a, b) => a + (b - mean) ** 2, 0) / (nums.length - 1) }; } case 11: { // VAR.P - if (nums.length === 0) return ErrNode.VALUE; + if (nums.length === 0) return ErrNode.DIV0; const mean = nums.reduce((a, b) => a + b, 0) / nums.length; return { t: 'num', v: nums.reduce((a, b) => a + (b - mean) ** 2, 0) / nums.length }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-math.ts` around lines 2084 - 2120, The AGGREGATE switch (variable fn) currently returns ErrNode.VALUE for insufficient-sample errors in cases 7 (STDEV.S), 8 (STDEV.P), 10 (VAR.S) and 11 (VAR.P); change those early-return branches to return ErrNode.DIV0 to match SUBTOTAL/Google Sheets behavior, e.g. replace "if (nums.length < 2) return ErrNode.VALUE" with "return ErrNode.DIV0" in case 7 and 10, and replace "if (nums.length === 0) return ErrNode.VALUE" with "return ErrNode.DIV0" in case 8 and 11; keep the rest of the calculations unchanged. Also confirm whether you intend to implement functions 13–19 (MODE/LARGE/SMALL/PERCENTILE/QUARTILE/etc.) because MEDIAN (12) is currently the only extra implemented item and any missing function_nums will fall through to the default ErrNode.VALUE.
2359-2372:⚠️ Potential issue | 🟠 Major
LOG10must return#NUM!for non-positive input, not#VALUE!.
LNandLOGboth returnErrNode.NUMwhen their arguments are ≤ 0, butLOG10incorrectly returnsErrNode.VALUEfor the same condition. Google Sheets returns#NUM!for bothLOG10(0)andLOG10(-1), making this an error-type mismatch that breaks consistency across the logarithm functions.Fix
- if (n.v <= 0) return ErrNode.VALUE; + if (n.v <= 0) return ErrNode.NUM;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-math.ts` around lines 2359 - 2372, log10Func currently returns ErrNode.VALUE for non-positive inputs but should return ErrNode.NUM to match LN/LOG and Google Sheets behavior; locate the function log10Func and change the branch that checks n.v <= 0 to return ErrNode.NUM instead of ErrNode.VALUE (keeping the rest of the flow: obtain args via ctx.args(), map the argument using NumberArgs.map(visit(exprs[0]), grid), and preserve existing error propagation for n.t === 'err').
2270-2300:⚠️ Potential issue | 🟡 MinorChange
ErrNode.VALUEtoErrNode.NUMfor singular matrix detection.At line 2281, the singular matrix detection returns
ErrNode.VALUE, but Google Sheets reports#NUM!forMINVERSEof a singular square matrix. This is a numeric-domain failure (analogous to other#NUM!mappings throughout the codebase), not a type/shape error. The non-matrix/non-square branches at lines 2251/2254 correctly useErrNode.VALUE.🛠️ Proposed fix
- if (maxVal < 1e-12) return ErrNode.VALUE; // Singular + if (maxVal < 1e-12) return ErrNode.NUM; // Singular matrix → `#NUM`!🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-math.ts` around lines 2270 - 2300, In the Gauss-Jordan elimination inside the MINVERSE implementation, the singular-matrix check currently returns ErrNode.VALUE; change that to ErrNode.NUM so a singular square matrix yields the numeric error used for MINVERSE; specifically replace the return at the pivot-check (the spot using maxVal < 1e-12 within the loop in the Gauss-Jordan code that references aug, maxRow/maxVal and pivot) to return ErrNode.NUM instead of ErrNode.VALUE.
♻️ Duplicate comments (1)
packages/sheets/src/formula/functions-text.ts (1)
784-789:⚠️ Potential issue | 🟠 MajorKeep
CHAR’s expanded code-point range consistent with the text stack.
String.fromCodePointcan now return supplementary characters, butLEN,LEFT,RIGHT,MID, andCODEstill operate on UTF-16 code units. That leaves formulas likeLEN(CHAR(128512)),LEFT(CHAR(128512), 1), andCODE(CHAR(128512))inconsistent with the newCHARrange.🔎 Read-only verification
#!/bin/bash set -euo pipefail node - <<'NODE' const s = String.fromCodePoint(0x1F600); console.log({ rendered: s, length: s.length, leftOneCodeUnit: JSON.stringify(s.slice(0, 1)), charCodeAt0: s.charCodeAt(0), codePointAt0: s.codePointAt(0), }); NODE rg -n -C2 'str\.v\.(length|slice|charCodeAt)\b|String\.fromCodePoint' -- 'packages/sheets/src/formula/functions-text.ts'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-text.ts` around lines 784 - 789, The CHAR implementation currently allows code points up to 1114111 and uses String.fromCodePoint, which creates supplementary characters incompatible with the rest of the text stack; restrict the valid range to UTF-16 code units by changing the upper bound check from 1114111 to 0xFFFF (65535) and replace String.fromCodePoint(code) with String.fromCharCode(code) (keep the existing check that returns ErrNode.NUM), referencing the local variable code, the ErrNode.NUM return, and the current String.fromCodePoint usage so the CHAR result is consistent with LEN/LEFT/RIGHT/MID/CODE behavior.
🧹 Nitpick comments (1)
packages/sheets/src/model/worksheet/input.ts (1)
347-350: Centralized error detection — behavior now auto-picks up#NULL!,#NAME?,#NUM!.The cast-to-
readonly string[]is the idiomatic workaround forArray.includesbeing typed with the element type. If this pattern shows up elsewhere, a tiny helper likeisErrValue(v: string): v is ErrValuealongsideErrValueswould let call sites drop the cast, but it's not essential here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/model/worksheet/input.ts` around lines 347 - 350, The code in defaultAlign uses a cast "(ErrValues as readonly string[]).includes(rawValue)" to detect error strings; replace this pattern with a small type-guard helper and use it instead: add an isErrValue(v: string): v is ErrValue function that checks membership against ErrValues (using includes without casting), then call isErrValue(rawValue) inside defaultAlign to return 'center' for error values; update other call sites to use isErrValue as needed so you can drop the cast everywhere (look for ErrValues and defaultAlign to locate places to change).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/sheets/src/formula/functions-math.ts`:
- Around line 731-733: The ATAN2 implementation currently returns ErrNode.VALUE
when both inputs are zero; update the branch in the ATAN2 function that checks
"if (x.v === 0 && y.v === 0)" to return ErrNode.DIV0 instead of ErrNode.VALUE so
ATAN2(0, 0) yields `#DIV/0`! (matching Google Sheets and the other divisor-zero
cases like MOD/QUOTIENT/CSCH/COTH).
In `@packages/sheets/src/formula/functions-statistical.ts`:
- Around line 3488-3493: The code calls Math.log on knownY (used in
GROWTH/LOGEST) without validating values, so zero/negative entries produce
-Infinity/NaN; update the knownY handling in the GROWTH/LOGEST paths (symbols:
knownY, lnY, linearRegression, GROWTH, LOGEST) to first check for any
non-positive value (e.g., if (knownY.some(y => y <= 0)) ) and return the
spreadsheet domain error (ErrNode.NUM) before computing lnY or calling
linearRegression; apply the same check to the other identical block referenced
(around the 3572–3576 block).
---
Outside diff comments:
In `@packages/sheets/src/formula/functions-engineering.ts`:
- Around line 352-367: The numeric-domain error handling in
functions-engineering.ts must return ErrNode.NUM (representing `#NUM`!) instead of
ErrNode.VALUE for DEC2* conversions: change the out-of-range check that
currently returns ErrNode.VALUE when val < -549755813888 || val > 549755813887
to return ErrNode.NUM, and change the places-too-small branch (the check if p <
hex.length) that returns ErrNode.VALUE to return ErrNode.NUM; apply the same
replacement in the other DEC2* blocks referenced (the similar branches around
the blocks handling ranges and places at the locations corresponding to the
other diffs, e.g. the blocks around lines 410-425 and 468-483) while leaving
existing early error propagation (places.t === 'err') unchanged.
In `@packages/sheets/src/formula/functions-financial.ts`:
- Around line 1258-1780: Several financial functions return ErrNode.VALUE (i.e.,
`#VALUE`!) for zero denominators or numeric-domain violations but per PR guidance
and Google Sheets behavior they should return ErrNode.NUM (`#NUM`!); update the
error returns in DISC (discountFunc), YIELDDISC (yielddiscFunc), RECEIVED
(receivedFunc), INTRATE (intrateFunc), PRICEMAT (pricematFunc), YIELDMAT
(yieldmatFunc), PDURATION (pdurationFunc) and RRI (rriFunc) to return
ErrNode.NUM where the code currently returns ErrNode.VALUE (or does
numeric-domain checks), and change non-convergence/error paths in RATE, IRR, and
XIRR implementations (functions named rateFunc, irrFunc, xirrFunc) to return
ErrNode.NUM instead of ErrNode.VALUE; ensure you only replace the specific error
return values and keep the existing guard checks and control flow intact.
In `@packages/sheets/src/formula/functions-math.ts`:
- Around line 2084-2120: The AGGREGATE switch (variable fn) currently returns
ErrNode.VALUE for insufficient-sample errors in cases 7 (STDEV.S), 8 (STDEV.P),
10 (VAR.S) and 11 (VAR.P); change those early-return branches to return
ErrNode.DIV0 to match SUBTOTAL/Google Sheets behavior, e.g. replace "if
(nums.length < 2) return ErrNode.VALUE" with "return ErrNode.DIV0" in case 7 and
10, and replace "if (nums.length === 0) return ErrNode.VALUE" with "return
ErrNode.DIV0" in case 8 and 11; keep the rest of the calculations unchanged.
Also confirm whether you intend to implement functions 13–19
(MODE/LARGE/SMALL/PERCENTILE/QUARTILE/etc.) because MEDIAN (12) is currently the
only extra implemented item and any missing function_nums will fall through to
the default ErrNode.VALUE.
- Around line 2359-2372: log10Func currently returns ErrNode.VALUE for
non-positive inputs but should return ErrNode.NUM to match LN/LOG and Google
Sheets behavior; locate the function log10Func and change the branch that checks
n.v <= 0 to return ErrNode.NUM instead of ErrNode.VALUE (keeping the rest of the
flow: obtain args via ctx.args(), map the argument using
NumberArgs.map(visit(exprs[0]), grid), and preserve existing error propagation
for n.t === 'err').
- Around line 2270-2300: In the Gauss-Jordan elimination inside the MINVERSE
implementation, the singular-matrix check currently returns ErrNode.VALUE;
change that to ErrNode.NUM so a singular square matrix yields the numeric error
used for MINVERSE; specifically replace the return at the pivot-check (the spot
using maxVal < 1e-12 within the loop in the Gauss-Jordan code that references
aug, maxRow/maxVal and pivot) to return ErrNode.NUM instead of ErrNode.VALUE.
In `@packages/sheets/src/formula/functions-statistical.ts`:
- Around line 1266-1305: The PERCENTRANK implementation can divide by zero when
vals has a single numeric value (n === 1); add a guard after computing n (and
before any division by n - 1) to detect n === 1 and return a spreadsheet error
(e.g. ErrNode.DIV0) so we don't compute rank = smaller / (n - 1) or do
interpolation; update the block surrounding vars n, smaller, idx, and rank in
functions-statistical.ts to return ErrNode.DIV0 when n === 1.
- Around line 3106-3124: The Z.TEST implementation can produce NaN when sigma is
omitted for a single sample and must reject non-positive explicit sigma; update
the block in the function using collectNumericValues, exprs, NumberArgs.map and
sigma so that if dataResult.length - 1 <= 0 (i.e. single data point and no
explicit sigma) you return ErrNode.DIV0 (or the project's appropriate
divide-by-zero error) instead of computing variance, and if an explicit sigma
(from NumberArgs.map(visit(exprs[2]))) is provided validate that s.v is finite
and > 0 and return ErrNode.DIV0 (or ErrNode.NUM per project convention) when
not, before using sigma as a divisor; keep the rest of the z calculation and
final return using normCdf unchanged.
---
Duplicate comments:
In `@packages/sheets/src/formula/functions-text.ts`:
- Around line 784-789: The CHAR implementation currently allows code points up
to 1114111 and uses String.fromCodePoint, which creates supplementary characters
incompatible with the rest of the text stack; restrict the valid range to UTF-16
code units by changing the upper bound check from 1114111 to 0xFFFF (65535) and
replace String.fromCodePoint(code) with String.fromCharCode(code) (keep the
existing check that returns ErrNode.NUM), referencing the local variable code,
the ErrNode.NUM return, and the current String.fromCodePoint usage so the CHAR
result is consistent with LEN/LEFT/RIGHT/MID/CODE behavior.
---
Nitpick comments:
In `@packages/sheets/src/model/worksheet/input.ts`:
- Around line 347-350: The code in defaultAlign uses a cast "(ErrValues as
readonly string[]).includes(rawValue)" to detect error strings; replace this
pattern with a small type-guard helper and use it instead: add an isErrValue(v:
string): v is ErrValue function that checks membership against ErrValues (using
includes without casting), then call isErrValue(rawValue) inside defaultAlign to
return 'center' for error values; update other call sites to use isErrValue as
needed so you can drop the cast everywhere (look for ErrValues and defaultAlign
to locate places to change).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6d9f2b5e-e55b-4ef8-b4af-c2fc2e4c8441
📒 Files selected for processing (18)
docs/design/sheets/formula.mdpackages/sheets/src/formula/arguments.tspackages/sheets/src/formula/formula.tspackages/sheets/src/formula/functions-database.tspackages/sheets/src/formula/functions-date.tspackages/sheets/src/formula/functions-engineering.tspackages/sheets/src/formula/functions-financial.tspackages/sheets/src/formula/functions-helpers.tspackages/sheets/src/formula/functions-info.tspackages/sheets/src/formula/functions-logical.tspackages/sheets/src/formula/functions-lookup.tspackages/sheets/src/formula/functions-math.tspackages/sheets/src/formula/functions-statistical.tspackages/sheets/src/formula/functions-text.tspackages/sheets/src/model/worksheet/calculator.tspackages/sheets/src/model/worksheet/input.tspackages/sheets/src/model/worksheet/shifting.tspackages/sheets/test/formula/formula.test.ts
✅ Files skipped from review due to trivial changes (1)
- docs/design/sheets/formula.md
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/sheets/test/formula/formula.test.ts
Summary
Changes:
Test coverage added for every corrected error path.
Why
Previously the engine returned #VALUE! for numeric domain violations (e.g. SQRT of a negative, LOG of zero, FACT of a negative integer) and #VALUE! for division-by-zero in MOD/QUOTIENT, where Google Sheets returns #NUM! and #DIV/0! respectively. This caused ERROR.TYPE to always return #VALUE! (code 3) for inputs that Google Sheets maps to codes 2 or 6.
Linked Issues
Fixes #
Verification
CI automatically posts a verification summary comment on this PR with
per-lane results for both
verify:selfandverify:integration.Skip reason (if applicable):
Risk Assessment
Notes for Reviewers
Summary by CodeRabbit
New Features
#NULL!,#NAME?,#NUM!) for finer-grained error reporting.Bug Fixes
#NAME?; many failures now return consistent error codes (e.g.,#NUM!,#DIV/0!,#VALUE!,#N/A).Tests
Documentation