diff --git a/packages/pine/src/task.ts b/packages/pine/src/task.ts index 1afcf9b8..f972a5cf 100644 --- a/packages/pine/src/task.ts +++ b/packages/pine/src/task.ts @@ -23,14 +23,48 @@ export const validTaskValue = (val: any) => { ); }; +/** + * Bind `this` for task functions to parent object. + * + * @param {object} obj + * @param {function} fn + * + * @returns {object|boolean} + */ +const bindTask = (obj: PineFile, task: any): PineFile | boolean => { + if (!validTaskValue(task)) { + return false; + } + + if (!isObject(task) || !task._) { + return { ...task, default: bindTask(task, task.default) }; + } + + return { + ...task, + _: task._.bind( + new Proxy(obj, { + get(target: PineFile, handler: string) { + if ( + isObject(target[handler]) && + typeof target[handler]._ === 'function' + ) { + return target[handler]._; + } + }, + }) + ), + }; +}; + /** * Resolve task function by name. * - * @param {string} obj + * @param {object} obj * @param {string} key * @param {string} sep * - * @returns {function|boolean} + * @returns {function|object|boolean} */ export const resolveTask = (obj: PineFile, key: string, sep = ':'): any => { if (!key) { @@ -38,11 +72,13 @@ export const resolveTask = (obj: PineFile, key: string, sep = ':'): any => { } const properties = (Array.isArray(key) ? key : key.split(sep)) as string[]; - const task = properties.reduce((prev: any[], cur: string) => { - return prev[cur] || false; - }, obj as any) as any; + const task = properties.reduce( + (prev: PineFile, cur: string) => + (bindTask(prev, prev[cur]) || {}) as PineFile, + obj + ); - if (!isObject(task) && !validTaskValue(task)) { + if (!validTaskValue(task)) { return false; } diff --git a/packages/pine/test/task.test.ts b/packages/pine/test/task.test.ts index a23eff4d..2f42c6ca 100644 --- a/packages/pine/test/task.test.ts +++ b/packages/pine/test/task.test.ts @@ -16,7 +16,29 @@ describe('task', () => { i: () => true, }, j: { - _: () => true, + default: () => true, + }, + m: { + n: () => true, + default() { + return this.n(); + }, + o: { + p() { + return true; + }, + q() { + return this.p(); + }, + }, + }, + r: { + s() { + return true; + }, + t() { + return this.t(); + }, }, }); @@ -48,7 +70,7 @@ describe('task', () => { }, { input: 'j', - type: 'function', + type: 'object', output: true, }, { @@ -61,6 +83,26 @@ describe('task', () => { type: 'boolean', output: false, }, + { + input: 'm', + type: 'object', + output: true, + }, + { + input: 'm:n', + type: 'function', + output: true, + }, + { + input: 'm:o:q', + type: 'function', + output: true, + }, + { + input: 'r:s', + type: 'function', + output: true, + }, { input: undefined, type: 'boolean', @@ -71,7 +113,10 @@ describe('task', () => { tests.forEach((test) => { const output = resolveTask(tasks, test.input as any); expect(typeof output).toBe(test.type); - expect(output ? output() : output).toBe(test.output); + + const fn = + typeof output === 'object' && output.default ? output.default : output; + expect(typeof fn === 'function' ? fn() : output).toBe(test.output); }); });