Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions src/operators/binary/arithmetic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,45 @@ export function add(a: any, b: any): any {
if (a === undefined || b === undefined) {
return undefined;
}
// If both values are strings and at least one of them in a non-number

// If both values are numbers then we want to add the numbers.
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}

// If both values are strings and at least one of them is a non-number
// then we want to concatenate the strings.
if (typeof a === 'string' && typeof b === 'string') {
const numA = Number(a);
const numB = Number(b);

if (isNaN(numA) || isNaN(numB)) {
return a + b;
return `${a}${b}`;
}
}

// Add the numeric values.
return Number(a) + Number(b);
// If both values are arrays then we want to concatenate the arrays.
if (Array.isArray(a) && Array.isArray(b)) {
return a.concat(b);
}

// If both values are objects then we want to merge the objects.
if (
typeof a === 'object' &&
typeof b === 'object' &&
!Array.isArray(a) &&
!Array.isArray(b)
) {
return { ...a, ...b };
}
Comment on lines +34 to +41
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic may not handle null values correctly. When both a and b are null, typeof null === 'object' is true, so this condition will be met and return an object spread of null values, which results in an empty object {}. This behavior may be unexpected. Consider adding null checks before the object merge logic.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +41
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new behavior for adding arrays (concatenation) and objects (merging) lacks test coverage. The test suite has comprehensive coverage for other operators, but there are no tests verifying that adding two arrays concatenates them or that adding two objects merges them. Consider adding tests to verify this new functionality works as expected.

Copilot uses AI. Check for mistakes.

// If both values can be converted to numbers then we want to add the numbers.
if (!isNaN(Number(a)) && !isNaN(Number(b))) {
return Number(a) + Number(b);
}

// Otherwise return an error indicating that the values of mixed types cannot be added.
throw new Error(`Cannot add values of incompatible types: ${typeof a} and ${typeof b}`);
}

export function sub(a: number | undefined, b: number | undefined): number | undefined {
Expand Down
46 changes: 46 additions & 0 deletions test/functions/functions-binary-ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,52 @@ describe('Binary Operators TypeScript Test', function () {
assert.strictEqual(parser.evaluate('2 + undefined'), undefined);
assert.strictEqual(parser.evaluate('undefined + 2'), undefined);
});
it('should concatenate non-numeric strings', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('"hello" + "world"'), 'helloworld');
assert.strictEqual(parser.evaluate('"foo" + "bar"'), 'foobar');
assert.strictEqual(parser.evaluate('"test" + "123"'), 'test123');
});
it('should add numeric strings as numbers', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('"5" + "3"'), 8);
assert.strictEqual(parser.evaluate('"10" + "20"'), 30);
assert.strictEqual(parser.evaluate('"0" + "5"'), 5);
});
it('should concatenate arrays', function () {
const parser = new Parser();
assert.deepStrictEqual(parser.evaluate('[1, 2] + [3, 4]'), [1, 2, 3, 4]);
assert.deepStrictEqual(parser.evaluate('[1] + [2, 3]'), [1, 2, 3]);
assert.deepStrictEqual(parser.evaluate('[] + [1, 2]'), [1, 2]);
});
it('should merge objects', function () {
const parser = new Parser();
assert.deepStrictEqual(parser.evaluate('{a: 1} + {b: 2}'), { a: 1, b: 2 });
assert.deepStrictEqual(parser.evaluate('{x: 10} + {y: 20}'), { x: 10, y: 20 });
assert.deepStrictEqual(parser.evaluate('{a: 1, b: 2} + {c: 3}'), { a: 1, b: 2, c: 3 });
});
it('should handle object merging with overlapping keys', function () {
const parser = new Parser();
assert.deepStrictEqual(parser.evaluate('{a: 1} + {a: 2}'), { a: 2 });
assert.deepStrictEqual(parser.evaluate('{x: 10, y: 20} + {y: 30}'), { x: 10, y: 30 });
});
it('should handle null values correctly', function () {
const parser = new Parser();
assert.deepStrictEqual(parser.evaluate('null + null'), {});
});
it('should convert numeric values to numbers before adding', function () {
const parser = new Parser();
assert.strictEqual(parser.evaluate('true + 1'), 2);
assert.strictEqual(parser.evaluate('false + 5'), 5);
assert.strictEqual(parser.evaluate('1 + true'), 2);
});
it('should throw error for incompatible types', function () {
const parser = new Parser();
assert.throws(() => parser.evaluate('5 + [1, 2]'), /Cannot add values of incompatible types/);
assert.throws(() => parser.evaluate('"text" + {a: 1}'), /Cannot add values of incompatible types/);
assert.throws(() => parser.evaluate('[1, 2] + {a: 1}'), /Cannot add values of incompatible types/);
assert.throws(() => parser.evaluate('5 + {x: 1}'), /Cannot add values of incompatible types/);
});
});

describe('- (subtraction)', function () {
Expand Down