Add Operator functions and move UNIQUE/CONCAT to Operator#149
Conversation
- Move UNIQUE in lookup and CONCAT in text to operator - Add ADDRESS, CHOOSE, COLUMN, COLUMNS, FORMULATEXT, GETPIVOTDATA, HLOOKUP, INDEX, INDIRECT, LOOKUP, MATCH, OFFSET, ROW, ROWS, SHEET, VLOOKUP, XLOOKUP funtions to Operator
Verification: verify:selfResult: ✅ PASS in 118.3s
Verification: verify:integrationResult: ✅ PASS |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
📝 WalkthroughWalkthroughCreated a new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (6)
packages/sheets/test/formula/function-catalog.test.ts (1)
419-431: Add representative Operator catalog assertions.Line 427 verifies that some
Operatorcategory exists, but it does not lock down the new catalog entries or the CONCAT/UNIQUE category move. Consider adding explicit assertions such asfindFunction('ADD')!.category,findFunction('CONCAT')!.category, andfindFunction('UNIQUE')!.categoryresolving toOperator. As per coding guidelines,**/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/test/formula/function-catalog.test.ts` around lines 419 - 431, Add explicit assertions to lock down the Operator category entries: call findFunction('ADD'), findFunction('CONCAT') and findFunction('UNIQUE') and assert their .category resolves to 'Operator' (i.e., expect(findFunction('ADD')!.category).toEqual('Operator'), etc.). Add these assertions in the same test near the listFunctionCategories() check so the test validates the catalog entries and the CONCAT/UNIQUE category assignment using the existing findFunction helper.packages/sheets/src/formula/functions-operator.ts (4)
31-52:compareScalarsreturn type declares an unreachableboolvariant.The declared return type is
number | { t: 'bool'; v: boolean } | ErrNode, but no path returns aboolnode — onlynumberorErrNode. That then forces the awkwardtypeof cmp === 'object' && 't' in cmpcheck plusas ErrNodecast at the call sites (Line 213, and again at Lines 269–270). Tightening the return type tonumber | ErrNoderemoves the dead variant and lets the callers use a simpletypeof cmp !== 'number'narrow without casts.♻️ Proposed change
-function compareScalars( - left: EvalNode, - right: EvalNode, -): number | { t: 'bool'; v: boolean } | ErrNode { +function compareScalars( + left: EvalNode, + right: EvalNode, +): number | ErrNode {and at call sites:
- if (typeof cmp === 'object' && 't' in cmp) return cmp as ErrNode; + if (typeof cmp !== 'number') return cmp;- if (typeof cmpLower === 'object') return cmpLower as ErrNode; - if (typeof cmpUpper === 'object') return cmpUpper as ErrNode; + if (typeof cmpLower !== 'number') return cmpLower; + if (typeof cmpUpper !== 'number') return cmpUpper;Also applies to: 213-213
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-operator.ts` around lines 31 - 52, The compareScalars function's declared return type includes an unused "{ t: 'bool'; v: boolean }" variant; change its signature to return number | ErrNode (remove the bool node variant) and keep the implementation returning only numbers or ErrNode; then update call sites that handle its result (the comparisons that currently test for an object or cast to ErrNode) to simply use typeof cmp !== 'number' to detect errors and narrow types without casts (look for usages of compareScalars and the existing object/'t' in cmp checks and replace them with a numeric-type check).
9-23: DRY:resolveScalarduplicatesresolveValuefromformula.ts.The JSDoc explicitly says this mirrors
formula.ts'sresolveValue. Having two copies of the empty/TRUE/FALSE/Number coercion will drift over time (e.g., one is updated to handle a new literal and the other isn't), which would silently change comparison semantics between=A1<B1and=LT(A1,B1). Prefer exporting the existing helper fromformula.tsand importing it here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-operator.ts` around lines 9 - 23, The resolveScalar function duplicates coercion logic already implemented in resolveValue (formula.ts); remove resolveScalar, export or make resolveValue available from formula.ts, import resolveValue into this module, and replace all uses of resolveScalar with resolveValue (ensuring EvalNode and Grid types are imported or compatible). Verify signatures match (node: EvalNode, grid?: Grid) and update any call sites in functions-operator.ts to pass the same args so comparison semantics remain consistent.
200-223: Exhaustiveswitchlacks a return outside the cases.
comparisonFunc's arrow returns inside eachcase, but there is no trailingreturn/throw. UndernoImplicitReturns/strict settings this can fail to type-check; even if it compiles today, a future addition to theopunion would silently fall through toundefined. Add an exhaustive fallback.🛡️ Proposed change
switch (op) { case 'eq': return { t: 'bool', v: cmp === 0 }; case 'ne': return { t: 'bool', v: cmp !== 0 }; case 'lt': return { t: 'bool', v: cmp < 0 }; case 'lte': return { t: 'bool', v: cmp <= 0 }; case 'gt': return { t: 'bool', v: cmp > 0 }; case 'gte': return { t: 'bool', v: cmp >= 0 }; } + const _exhaustive: never = op; + return _exhaustive;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-operator.ts` around lines 200 - 223, The function comparisonFunc currently switches on op ('eq'|'ne'|'lt'|'lte'|'gt'|'gte') and returns inside each case but has no fallback return; add an exhaustive fallback after the switch in the returned arrow function to avoid undefined returns — either throw a descriptive Error mentioning the unexpected op or return an ErrNode/EvalNode indicating an internal error. Update comparisonFunc (the inner returned function that calls twoArgs, resolveScalar and compareScalars) so any unknown op value is handled deterministically (use the same ErrNode shape used elsewhere) to satisfy noImplicitReturns and prevent silent undefined.
315-333: Use a proper type signature instead ofany[]for the exported operator registry.
operatorEntriesuses[string, (...args: any[]) => EvalNode][], which loses type safety. Every operator function in this file already declares the correct signature withctx: FunctionContext, visit: (tree: ParseTree) => EvalNode, grid?: Grid. Define a shared type alias and apply it tooperatorEntriesso misregistration is caught at compile time:+type OperatorFn = ( + ctx: FunctionContext, + visit: (tree: ParseTree) => EvalNode, + grid?: Grid, +) => EvalNode; + -export const operatorEntries: [string, (...args: any[]) => EvalNode][] = [ +export const operatorEntries: [string, OperatorFn][] = [All required imports (
FunctionContext,ParseTree,Grid) are already present at the top of this file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/src/formula/functions-operator.ts` around lines 315 - 333, Create a proper function type alias (e.g., OperatorFn) matching the real signature used by all operator implementations: (ctx: FunctionContext, visit: (tree: ParseTree) => EvalNode, grid?: Grid) => EvalNode, then change the operator registry declaration for operatorEntries from [string, (...args: any[]) => EvalNode][] to [string, OperatorFn][] so misregistered entries (e.g., addFunc, minusFunc, concatFunc, uniqueFunc, etc.) are type-checked at compile time; keep the alias and the updated operatorEntries export in this file so existing imports (FunctionContext, ParseTree, Grid) are reused.packages/sheets/test/formula/formula.test.ts (1)
3700-3830: LGTM.Good, focused coverage for the new operator-backed functions. One optional enhancement to consider: none of the
ISBETWEENcases exercise cell-reference inputs or error-propagation (e.g.,ISBETWEEN(A1, 1, 10)withA1unset, or with a#DIV/0!value/bound). Adding a case or two there would lock in theresolveScalar/error-propagation paths inisbetweenFunc.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sheets/test/formula/formula.test.ts` around lines 3700 - 3830, Add tests to exercise ISBETWEEN with cell-reference inputs and error propagation: add cases using evaluate('=ISBETWEEN(A1,1,10)', grid) where A1 is unset (should behave like blank/zero per resolveScalar) and where A1 or bounds contain an error token (e.g., '#DIV/0!') to verify isbetweenFunc forwards errors; target the test block named "ISBETWEEN" and use the evaluate helper and isbetweenFunc semantics to assert correct TRUE/FALSE or error string results so resolveScalar/error-propagation paths are covered.
🤖 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/sheets/formula-coverage.md`:
- Line 45: Update the coverage table and summary to remove stale Operator rows
and reconcile totals: delete or update the outdated "ISBETWEEN" and the
"Operator functions (ADD, MINUS, MULTIPLY, etc.)" out-of-scope note so they no
longer contradict the Operator row that shows 100% coverage, then recalculate
and update the unique-function count and summary totals to reflect the newly
included Operator entries (ensure references to "Operator", "ISBETWEEN", and the
"Operator functions (ADD, MINUS, MULTIPLY, etc.)" strings are updated/removed
consistently across the document).
In `@packages/sheets/src/formula/functions-operator.ts`:
- Around line 280-297: The concatFunc implementation correctly enforces a
minimum of 2 arguments (returns ErrNode.NA when args.expr().length < 2) but
tests only cover 2 and 3 args; add a unit test that invokes concat with a single
literal argument and asserts the result is `#N/A` (ErrNode.NA) to prevent
regressions—follow the existing test style used for concat's 2- and 3-argument
cases and assert the visited evaluation yields an error node representing NA.
---
Nitpick comments:
In `@packages/sheets/src/formula/functions-operator.ts`:
- Around line 31-52: The compareScalars function's declared return type includes
an unused "{ t: 'bool'; v: boolean }" variant; change its signature to return
number | ErrNode (remove the bool node variant) and keep the implementation
returning only numbers or ErrNode; then update call sites that handle its result
(the comparisons that currently test for an object or cast to ErrNode) to simply
use typeof cmp !== 'number' to detect errors and narrow types without casts
(look for usages of compareScalars and the existing object/'t' in cmp checks and
replace them with a numeric-type check).
- Around line 9-23: The resolveScalar function duplicates coercion logic already
implemented in resolveValue (formula.ts); remove resolveScalar, export or make
resolveValue available from formula.ts, import resolveValue into this module,
and replace all uses of resolveScalar with resolveValue (ensuring EvalNode and
Grid types are imported or compatible). Verify signatures match (node: EvalNode,
grid?: Grid) and update any call sites in functions-operator.ts to pass the same
args so comparison semantics remain consistent.
- Around line 200-223: The function comparisonFunc currently switches on op
('eq'|'ne'|'lt'|'lte'|'gt'|'gte') and returns inside each case but has no
fallback return; add an exhaustive fallback after the switch in the returned
arrow function to avoid undefined returns — either throw a descriptive Error
mentioning the unexpected op or return an ErrNode/EvalNode indicating an
internal error. Update comparisonFunc (the inner returned function that calls
twoArgs, resolveScalar and compareScalars) so any unknown op value is handled
deterministically (use the same ErrNode shape used elsewhere) to satisfy
noImplicitReturns and prevent silent undefined.
- Around line 315-333: Create a proper function type alias (e.g., OperatorFn)
matching the real signature used by all operator implementations: (ctx:
FunctionContext, visit: (tree: ParseTree) => EvalNode, grid?: Grid) => EvalNode,
then change the operator registry declaration for operatorEntries from [string,
(...args: any[]) => EvalNode][] to [string, OperatorFn][] so misregistered
entries (e.g., addFunc, minusFunc, concatFunc, uniqueFunc, etc.) are
type-checked at compile time; keep the alias and the updated operatorEntries
export in this file so existing imports (FunctionContext, ParseTree, Grid) are
reused.
In `@packages/sheets/test/formula/formula.test.ts`:
- Around line 3700-3830: Add tests to exercise ISBETWEEN with cell-reference
inputs and error propagation: add cases using evaluate('=ISBETWEEN(A1,1,10)',
grid) where A1 is unset (should behave like blank/zero per resolveScalar) and
where A1 or bounds contain an error token (e.g., '#DIV/0!') to verify
isbetweenFunc forwards errors; target the test block named "ISBETWEEN" and use
the evaluate helper and isbetweenFunc semantics to assert correct TRUE/FALSE or
error string results so resolveScalar/error-propagation paths are covered.
In `@packages/sheets/test/formula/function-catalog.test.ts`:
- Around line 419-431: Add explicit assertions to lock down the Operator
category entries: call findFunction('ADD'), findFunction('CONCAT') and
findFunction('UNIQUE') and assert their .category resolves to 'Operator' (i.e.,
expect(findFunction('ADD')!.category).toEqual('Operator'), etc.). Add these
assertions in the same test near the listFunctionCategories() check so the test
validates the catalog entries and the CONCAT/UNIQUE category assignment using
the existing findFunction helper.
🪄 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: 2287b3b9-0f3e-435b-99f0-17614a9086dc
📒 Files selected for processing (9)
docs/design/sheets/formula-coverage.mdpackages/sheets/src/formula/function-catalog.tspackages/sheets/src/formula/functions-helpers.tspackages/sheets/src/formula/functions-lookup.tspackages/sheets/src/formula/functions-operator.tspackages/sheets/src/formula/functions-text.tspackages/sheets/src/formula/functions.tspackages/sheets/test/formula/formula.test.tspackages/sheets/test/formula/function-catalog.test.ts
💤 Files with no reviewable changes (1)
- packages/sheets/src/formula/functions-text.ts
Summary
Why
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
Documentation