fix(core): Hand node reference results without an item shape to the engine (no-changelog) (#41083)

This commit is contained in:
Mike Repeć 2026-10-16 12:25:04 +00:00 committed by GitHub
parent d0cb582163
commit 1b02863c00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 0 deletions

View File

@ -508,11 +508,19 @@ function evalNodeRefCall(proxy: unknown, method: string, env: Env): unknown {
if (typeof fn !== 'function') throw new EngineFallbackError();
const result = bounded(fn.call(proxy));
// A node name that is an Object.prototype key resolves through a plain
// object lookup on the host. Only the item shapes stay native.
const expected = method === 'all' ? isArray(result) : result === undefined || isItem(result);
if (!expected) throw new EngineFallbackError();
env.charge(isArray(result) ? result.length : 1);
return result;
}
const isItem = (value: unknown): boolean => isObj(value) && !isArray(value);
/**
* A captured Luxon getter or zero-argument method on `$now`/`$today`. The
* root is the proxy's own DateTime (anything else is the engine's), and the

View File

@ -731,6 +731,32 @@ describe('Expression - fast native evaluation parity', () => {
expect(nativeOn(expr, visits(fits + 2))).toEqual({ handled: false });
expect(nativeOn(expr, visits(fits))).toEqual({ handled: true, value: false });
});
// `$('hasOwnProperty')` resolves through a plain object lookup on the
// host and hands back Object.prototype.hasOwnProperty, not item data.
test('a node reference call that returns no item shape hands off to the engine', () => {
const { hasOwnProperty } = Object.prototype;
const prototypeNode = {
$: () => ({ all: () => hasOwnProperty, first: () => hasOwnProperty }),
};
expect(nativeOn("{{ $('hasOwnProperty').all().name }}", prototypeNode)).toEqual({
handled: false,
});
expect(nativeOn("{{ $('hasOwnProperty').first() }}", prototypeNode)).toEqual({
handled: false,
});
const item = { json: { a: 1 } };
const node = { $: () => ({ all: () => [item], first: () => item, last: () => undefined }) };
expect(nativeOn("{{ $('Source').all().length }}", node)).toEqual({ handled: true, value: 1 });
expect(nativeOn("{{ $('Source').first().json.a }}", node)).toEqual({
handled: true,
value: 1,
});
expect(nativeOn("{{ $('Source').last() }}", node)).toEqual({
handled: true,
value: undefined,
});
});
});
// extendSyntax rewrites calls to extension-named methods into extend()