-
Notifications
You must be signed in to change notification settings - Fork 1
Improve array function error messages with signatures and examples #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,15 +3,30 @@ | |
| * Handles array manipulation and processing operations | ||
| */ | ||
|
|
||
| /** | ||
| * Get a user-friendly type name for a value | ||
| */ | ||
| function getTypeName(value: unknown): string { | ||
| if (value === null) return 'null'; | ||
| if (Array.isArray(value)) return 'array'; | ||
| return typeof value; | ||
| } | ||
|
|
||
| export function filter(f: Function, a: any[] | undefined): any[] | undefined { | ||
| if (a === undefined) { | ||
| return undefined; | ||
| } | ||
| if (typeof f !== 'function') { | ||
| throw new Error('First argument to filter is not a function'); | ||
| throw new Error( | ||
| `filter(predicate, array) expects a function as first argument, got ${getTypeName(f)}.\n` + | ||
| 'Example: filter(x => x > 0, [1, -2, 3])' | ||
| ); | ||
| } | ||
| if (!Array.isArray(a)) { | ||
| throw new Error('Second argument to filter is not an array'); | ||
| throw new Error( | ||
| `filter(predicate, array) expects an array as second argument, got ${getTypeName(a)}.\n` + | ||
| 'Example: filter(x => x > 0, [1, -2, 3])' | ||
| ); | ||
| } | ||
| return a.filter(function (x: any, i: number): any { | ||
| return f(x, i); | ||
|
|
@@ -23,10 +38,16 @@ export function fold(f: Function, init: any, a: any[] | undefined): any { | |
| return undefined; | ||
| } | ||
| if (typeof f !== 'function') { | ||
| throw new Error('First argument to fold is not a function'); | ||
| throw new Error( | ||
| `fold(reducer, initial, array) expects a function as first argument, got ${getTypeName(f)}.\n` + | ||
| 'Example: fold((acc, x) => acc + x, 0, [1, 2, 3])' | ||
| ); | ||
| } | ||
| if (!Array.isArray(a)) { | ||
| throw new Error('Second argument to fold is not an array'); | ||
| throw new Error( | ||
| `fold(reducer, initial, array) expects an array as third argument, got ${getTypeName(a)}.\n` + | ||
| 'Example: fold((acc, x) => acc + x, 0, [1, 2, 3])' | ||
| ); | ||
| } | ||
| return a.reduce(function (acc: any, x: any, i: number): any { | ||
| return f(acc, x, i); | ||
|
|
@@ -38,7 +59,10 @@ export function indexOf(target: any, s: string | any[] | undefined): number | un | |
| return undefined; | ||
| } | ||
| if (!(Array.isArray(s) || typeof s === 'string')) { | ||
| throw new Error('Second argument to indexOf is not a string or array'); | ||
| throw new Error( | ||
| `indexOf(target, arrayOrString) expects a string or array as second argument, got ${getTypeName(s)}.\n` + | ||
| 'Example: indexOf("b", ["a", "b", "c"]) or indexOf("o", "hello")' | ||
| ); | ||
| } | ||
|
|
||
| return s.indexOf(target); | ||
|
|
@@ -49,7 +73,10 @@ export function join(sep: string | undefined, a: any[] | undefined): string | un | |
| return undefined; | ||
| } | ||
| if (!Array.isArray(a)) { | ||
| throw new Error('Second argument to join is not an array'); | ||
| throw new Error( | ||
| `join(separator, array) expects an array as second argument, got ${getTypeName(a)}.\n` + | ||
| 'Example: join(", ", ["a", "b", "c"])' | ||
| ); | ||
| } | ||
|
|
||
| return a.join(sep); | ||
|
|
@@ -60,10 +87,16 @@ export function map(f: Function, a: any[] | undefined): any[] | undefined { | |
| return undefined; | ||
| } | ||
| if (typeof f !== 'function') { | ||
| throw new Error('First argument to map is not a function'); | ||
| throw new Error( | ||
| `map(mapper, array) expects a function as first argument, got ${getTypeName(f)}.\n` + | ||
| 'Example: map(x => x * 2, [1, 2, 3])' | ||
| ); | ||
| } | ||
| if (!Array.isArray(a)) { | ||
| throw new Error('Second argument to map is not an array'); | ||
| throw new Error( | ||
| `map(mapper, array) expects an array as second argument, got ${getTypeName(a)}.\n` + | ||
| 'Example: map(x => x * 2, [1, 2, 3])' | ||
| ); | ||
| } | ||
| return a.map(function (x: any, i: number): any { | ||
| return f(x, i); | ||
|
|
@@ -75,7 +108,10 @@ export function sum(array: (number | undefined)[] | undefined): number | undefin | |
| return undefined; | ||
| } | ||
| if (!Array.isArray(array)) { | ||
| throw new Error('Sum argument is not an array'); | ||
| throw new Error( | ||
| `sum(array) expects an array as argument, got ${getTypeName(array)}.\n` + | ||
| 'Example: sum([1, 2, 3, 4])' | ||
| ); | ||
| } | ||
| if (array.includes(undefined)) { | ||
| return undefined; | ||
|
|
@@ -91,7 +127,10 @@ export function count(array: any[] | undefined): number | undefined { | |
| return undefined; | ||
| } | ||
| if (!Array.isArray(array)) { | ||
| throw new Error('Count argument is not an array'); | ||
| throw new Error( | ||
| `count(array) expects an array as argument, got ${getTypeName(array)}.\n` + | ||
| 'Example: count([1, 2, 3, 4])' | ||
| ); | ||
| } | ||
| return array.length; | ||
| } | ||
|
|
@@ -106,10 +145,16 @@ export function find(f: Function, a: any[] | undefined): any { | |
| return undefined; | ||
| } | ||
| if (typeof f !== 'function') { | ||
| throw new Error('First argument to find is not a function'); | ||
| throw new Error( | ||
| `find(predicate, array) expects a function as first argument, got ${getTypeName(f)}.\n` + | ||
| 'Example: find(x => x > 2, [1, 2, 3, 4])' | ||
| ); | ||
| } | ||
| if (!Array.isArray(a)) { | ||
| throw new Error('Second argument to find is not an array'); | ||
| throw new Error( | ||
| `find(predicate, array) expects an array as second argument, got ${getTypeName(a)}.\n` + | ||
| 'Example: find(x => x > 2, [1, 2, 3, 4])' | ||
| ); | ||
| } | ||
| return a.find(function (x: any, i: number): any { | ||
| return f(x, i); | ||
|
|
@@ -121,10 +166,16 @@ export function some(f: Function, a: any[] | undefined): boolean | undefined { | |
| return undefined; | ||
| } | ||
| if (typeof f !== 'function') { | ||
| throw new Error('First argument to some is not a function'); | ||
| throw new Error( | ||
| `some(predicate, array) expects a function as first argument, got ${getTypeName(f)}.\n` + | ||
| 'Example: some(x => x > 2, [1, 2, 3, 4])' | ||
| ); | ||
| } | ||
| if (!Array.isArray(a)) { | ||
| throw new Error('Second argument to some is not an array'); | ||
| throw new Error( | ||
| `some(predicate, array) expects an array as second argument, got ${getTypeName(a)}.\n` + | ||
| 'Example: some(x => x > 2, [1, 2, 3, 4])' | ||
| ); | ||
| } | ||
| return a.some(function (x: any, i: number): any { | ||
| return f(x, i); | ||
|
|
@@ -136,10 +187,16 @@ export function every(f: Function, a: any[] | undefined): boolean | undefined { | |
| return undefined; | ||
| } | ||
| if (typeof f !== 'function') { | ||
| throw new Error('First argument to every is not a function'); | ||
| throw new Error( | ||
| `every(predicate, array) expects a function as first argument, got ${getTypeName(f)}.\n` + | ||
| 'Example: every(x => x > 0, [1, 2, 3, 4])' | ||
| ); | ||
| } | ||
| if (!Array.isArray(a)) { | ||
| throw new Error('Second argument to every is not an array'); | ||
| throw new Error( | ||
| `every(predicate, array) expects an array as second argument, got ${getTypeName(a)}.\n` + | ||
| 'Example: every(x => x > 0, [1, 2, 3, 4])' | ||
| ); | ||
| } | ||
| return a.every(function (x: any, i: number): any { | ||
| return f(x, i); | ||
|
|
@@ -151,7 +208,10 @@ export function unique(a: any[] | undefined): any[] | undefined { | |
| return undefined; | ||
| } | ||
| if (!Array.isArray(a)) { | ||
| throw new Error('Argument to unique is not an array'); | ||
| throw new Error( | ||
| `unique(array) expects an array as argument, got ${getTypeName(a)}.\n` + | ||
| 'Example: unique([1, 2, 2, 3, 3, 3])' | ||
| ); | ||
|
Comment on lines
+211
to
+214
|
||
| } | ||
| // Use Set to remove duplicates, then convert back to array | ||
| return Array.from(new Set(a)); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,175 @@ | ||||||||||||||||||||||||
| /* global describe, it */ | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import { Parser } from '../../index'; | ||||||||||||||||||||||||
| import { expect } from 'vitest'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| describe('Array Function Error Messages', function () { | ||||||||||||||||||||||||
| describe('filter()', function () { | ||||||||||||||||||||||||
| it('should provide user-friendly error when first argument is not a function', function () { | ||||||||||||||||||||||||
| const parser = new Parser(); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('filter(42, [1, 2, 3])')).toThrow( | ||||||||||||||||||||||||
| /filter\(predicate, array\) expects a function as first argument, got number/ | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('filter(42, [1, 2, 3])')).toThrow(/Example:/); | ||||||||||||||||||||||||
|
Comment on lines
+10
to
+13
|
||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| it('should provide user-friendly error when second argument is not an array', function () { | ||||||||||||||||||||||||
| const parser = new Parser(); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('f(x) = x > 0; filter(f, "not an array")')).toThrow( | ||||||||||||||||||||||||
| /filter\(predicate, array\) expects an array as second argument, got string/ | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('f(x) = x > 0; filter(f, "not an array")')).toThrow(/Example:/); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| describe('map()', function () { | ||||||||||||||||||||||||
| it('should provide user-friendly error when first argument is not a function', function () { | ||||||||||||||||||||||||
| const parser = new Parser(); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('map("not a function", [1, 2, 3])')).toThrow( | ||||||||||||||||||||||||
| /map\(mapper, array\) expects a function as first argument, got string/ | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('map("not a function", [1, 2, 3])')).toThrow(/Example:/); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| it('should provide user-friendly error when second argument is not an array', function () { | ||||||||||||||||||||||||
| const parser = new Parser(); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('f(x) = x * 2; map(f, 123)')).toThrow( | ||||||||||||||||||||||||
| /map\(mapper, array\) expects an array as second argument, got number/ | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
Comment on lines
+34
to
+39
|
||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| describe('fold()', function () { | ||||||||||||||||||||||||
| it('should provide user-friendly error when first argument is not a function', function () { | ||||||||||||||||||||||||
| const parser = new Parser(); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('fold(null, 0, [1, 2, 3])', { null: null })).toThrow( | ||||||||||||||||||||||||
| /fold\(reducer, initial, array\) expects a function as first argument, got null/ | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('fold(null, 0, [1, 2, 3])', { null: null })).toThrow(/Example:/); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| it('should provide user-friendly error when third argument is not an array', function () { | ||||||||||||||||||||||||
| const parser = new Parser(); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('f(a, b) = a + b; fold(f, 0, {a: 1})')).toThrow( | ||||||||||||||||||||||||
| /fold\(reducer, initial, array\) expects an array as third argument, got object/ | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
Comment on lines
+51
to
+56
|
||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| describe('reduce()', function () { | ||||||||||||||||||||||||
| it('should provide user-friendly error from fold when first argument is not a function', function () { | ||||||||||||||||||||||||
| const parser = new Parser(); | ||||||||||||||||||||||||
| expect(() => parser.evaluate('reduce(true, 0, [1, 2, 3])')).toThrow( | ||||||||||||||||||||||||
| /fold\(reducer, initial, array\) expects a function as first argument, got boolean/ | ||||||||||||||||||||||||
|
Comment on lines
+60
to
+63
|
||||||||||||||||||||||||
| it('should provide user-friendly error from fold when first argument is not a function', function () { | |
| const parser = new Parser(); | |
| expect(() => parser.evaluate('reduce(true, 0, [1, 2, 3])')).toThrow( | |
| /fold\(reducer, initial, array\) expects a function as first argument, got boolean/ | |
| it('should provide user-friendly error when first argument is not a function', function () { | |
| const parser = new Parser(); | |
| expect(() => parser.evaluate('reduce(true, 0, [1, 2, 3])')).toThrow( | |
| /reduce\(reducer, initial, array\) expects a function as first argument, got boolean/ |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test validates the main part of the find(...) error, but it doesn’t assert that the message includes the usage Example: line. Adding an Example: assertion here would better cover the new behavior introduced by this PR.
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither of the some(...) error tests asserts that the message contains the Example: line. Since this PR adds examples to these errors, consider asserting Example: is included for both cases to prevent regressions.
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither of the every(...) error tests asserts that the message contains the Example: line. Since this PR adds examples to these errors, consider asserting Example: is included for both cases to prevent regressions.
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test currently expects distinct(...) to throw an error message that starts with unique(array) .... Since distinct is user-facing, it would be clearer if the error message used the distinct(array) signature (and included an Example: line) instead of exposing the alias target.
| it('should provide user-friendly error from unique when argument is not an array', function () { | |
| const parser = new Parser(); | |
| expect(() => parser.evaluate('distinct({a: 1})')).toThrow( | |
| /unique\(array\) expects an array as argument, got object/ | |
| ); | |
| it('should provide user-friendly error when argument is not an array', function () { | |
| const parser = new Parser(); | |
| expect(() => parser.evaluate('distinct({a: 1})')).toThrow( | |
| /distinct\(array\) expects an array as argument, got object/ | |
| ); | |
| expect(() => parser.evaluate('distinct({a: 1})')).toThrow(/Example:/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reduce()is an alias forfold(), so any argument-type errors triggered viareduce(...)will now surface an error message that starts withfold(reducer, initial, array) .... That’s confusing for users who calledreduce. Consider havingreduce()do its own argument validation / error formatting (or catch and rethrow) so the message referencesreduce(reducer, initial, array)and provides areduce(...)example.