diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index 1c74fad47a0a3..ee75c6d8ca51f 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -69,7 +69,7 @@ import type { useRouter } from 'vue-router'; import { useTelemetry } from '@/composables/useTelemetry'; import { useProjectsStore } from '@/features/projects/projects.store'; -export function resolveParameter( +export function resolveParameter( parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[], opts: { targetItem?: TargetItem; @@ -79,7 +79,7 @@ export function resolveParameter( additionalKeys?: IWorkflowDataProxyAdditionalKeys; isForCredential?: boolean; } = {}, -): IDataObject | null { +): T | null { let itemIndex = opts?.targetItem?.itemIndex || 0; const workflow = getCurrentWorkflow(); @@ -115,7 +115,7 @@ export function resolveParameter( false, undefined, '', - ) as IDataObject; + ) as T; } const inputName = NodeConnectionType.Main; @@ -235,7 +235,7 @@ export function resolveParameter( false, {}, contextNode!.name, - ) as IDataObject; + ) as T; } export function resolveRequiredParameters( diff --git a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts index d1d1ca6d6008c..0f8f41c899286 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/__tests__/completions.test.ts @@ -119,14 +119,12 @@ describe('Top-level completions', () => { describe('Luxon method completions', () => { test('should return class completions for: {{ DateTime.| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce(DateTime); expect(completions('{{ DateTime.| }}')).toHaveLength(luxonStaticOptions().length); }); test('should return instance completions for: {{ $now.| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce(DateTime.now()); expect(completions('{{ $now.| }}')).toHaveLength( @@ -138,7 +136,6 @@ describe('Luxon method completions', () => { }); test('should return instance completions for: {{ $today.| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce(DateTime.now()); expect(completions('{{ $today.| }}')).toHaveLength( @@ -153,7 +150,6 @@ describe('Luxon method completions', () => { describe('Resolution-based completions', () => { describe('literals', () => { test('should return completions for string literal: {{ "abc".| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce('abc'); expect(completions('{{ "abc".| }}')).toHaveLength( @@ -164,7 +160,6 @@ describe('Resolution-based completions', () => { }); test('should return completions for boolean literal: {{ true.| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce(true); expect(completions('{{ true.| }}')).toHaveLength( @@ -173,7 +168,6 @@ describe('Resolution-based completions', () => { }); test('should properly handle string that contain dollar signs', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce("You 'owe' me 200$ "); const result = completions('{{ "You \'owe\' me 200$".| }}'); @@ -184,7 +178,6 @@ describe('Resolution-based completions', () => { }); test('should return completions for number literal: {{ (123).| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce(123); expect(completions('{{ (123).| }}')).toHaveLength( @@ -195,7 +188,6 @@ describe('Resolution-based completions', () => { }); test('should return completions for array literal: {{ [1, 2, 3].| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce([1, 2, 3]); expect(completions('{{ [1, 2, 3].| }}')).toHaveLength( @@ -204,7 +196,6 @@ describe('Resolution-based completions', () => { }); test('should return completions for Object methods: {{ Object.values({ abc: 123 }).| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce([123]); const found = completions('{{ Object.values({ abc: 123 }).| }}'); @@ -229,7 +220,6 @@ describe('Resolution-based completions', () => { describe('indexed access completions', () => { test('should return string completions for indexed access that resolves to string literal: {{ "abc"[0].| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce('a'); expect(completions('{{ "abc"[0].| }}')).toHaveLength( @@ -244,7 +234,7 @@ describe('Resolution-based completions', () => { const { $input } = mockProxy; test('should return completions when $input is used as a function parameter', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json.num); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json.num); const found = completions('{{ Math.abs($input.item.json.num1).| }}'); if (!found) throw new Error('Expected to find completions'); expect(found).toHaveLength( @@ -273,7 +263,7 @@ describe('Resolution-based completions', () => { }); test('should return completions for complex expression: {{ $execution.resumeUrl.includes($json.) }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce($input.item.json); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce($input.item?.json); const { $json } = mockProxy; const found = completions('{{ $execution.resumeUrl.includes($json.|) }}'); @@ -284,7 +274,7 @@ describe('Resolution-based completions', () => { }); test('should return completions for operation expression: {{ $now.day + $json. }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce($input.item.json); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce($input.item?.json); const { $json } = mockProxy; const found = completions('{{ $now.day + $json.| }}'); @@ -296,7 +286,7 @@ describe('Resolution-based completions', () => { }); test('should return completions for operation expression: {{ Math.abs($now.day) >= 10 ? $now : Math.abs($json.). }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json); const { $json } = mockProxy; const found = completions('{{ Math.abs($now.day) >= 10 ? $now : Math.abs($json.|) }}'); @@ -312,7 +302,7 @@ describe('Resolution-based completions', () => { const { $input } = mockProxy; test('should return bracket-aware completions for: {{ $input.item.json.str.|() }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json.str); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json.str); const found = completions('{{ $input.item.json.str.|() }}'); @@ -327,7 +317,7 @@ describe('Resolution-based completions', () => { }); test('should return bracket-aware completions for: {{ $input.item.json.num.|() }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json.num); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json.num); const found = completions('{{ $input.item.json.num.|() }}'); @@ -342,7 +332,7 @@ describe('Resolution-based completions', () => { }); test('should return bracket-aware completions for: {{ $input.item.json.arr.| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json.arr); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json.arr); const found = completions('{{ $input.item.json.arr.|() }}'); @@ -477,7 +467,6 @@ describe('Resolution-based completions', () => { }); test('should return completions for: {{ $input.all().| }}', () => { - // @ts-expect-error vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue([$input.item]); expect(completions('{{ $input.all().| }}')).toHaveLength( @@ -488,26 +477,26 @@ describe('Resolution-based completions', () => { }); test("should return completions for: '{{ $input.item.| }}'", () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json); expect(completions('{{ $input.item.| }}')).toHaveLength( - Object.keys($input.item.json).length + extensions({ typeName: 'object' }).length, + Object.keys($input.item?.json ?? {}).length + extensions({ typeName: 'object' }).length, ); }); test("should return completions for: '{{ $input.first().| }}'", () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.first().json); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.first()?.json); expect(completions('{{ $input.first().| }}')).toHaveLength( - Object.keys($input.first().json).length + extensions({ typeName: 'object' }).length, + Object.keys($input.first()?.json ?? {}).length + extensions({ typeName: 'object' }).length, ); }); test("should return completions for: '{{ $input.last().| }}'", () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.last().json); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.last()?.json); expect(completions('{{ $input.last().| }}')).toHaveLength( - Object.keys($input.last().json).length + extensions({ typeName: 'object' }).length, + Object.keys($input.last()?.json ?? {}).length + extensions({ typeName: 'object' }).length, ); }); @@ -520,7 +509,7 @@ describe('Resolution-based completions', () => { }); test('should return completions for: {{ $input.item.json.str.| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json.str); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json.str); expect(completions('{{ $input.item.json.str.| }}')).toHaveLength( extensions({ typeName: 'string' }).length + @@ -530,7 +519,7 @@ describe('Resolution-based completions', () => { }); test('should return completions for: {{ $input.item.json.num.| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json.num); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json.num); expect(completions('{{ $input.item.json.num.| }}')).toHaveLength( extensions({ typeName: 'number' }).length + @@ -540,7 +529,7 @@ describe('Resolution-based completions', () => { }); test('should return completions for: {{ $input.item.json.arr.| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json.arr); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json.arr); expect(completions('{{ $input.item.json.arr.| }}')).toHaveLength( extensions({ typeName: 'array' }).length + natives({ typeName: 'array' }).length, @@ -548,10 +537,10 @@ describe('Resolution-based completions', () => { }); test('should return completions for: {{ $input.item.json.obj.| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json.obj); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json.obj); expect(completions('{{ $input.item.json.obj.| }}')).toHaveLength( - Object.keys($input.item.json.obj).length + extensions({ typeName: 'object' }).length, + Object.keys($input.item?.json.obj ?? {}).length + extensions({ typeName: 'object' }).length, ); }); }); @@ -561,26 +550,26 @@ describe('Resolution-based completions', () => { ['{{ $input.item.json[| }}', '{{ $json[| }}'].forEach((expression) => { test(`should return completions for: ${expression}`, () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json); const found = completions(expression); if (!found) throw new Error('Expected to find completions'); - expect(found).toHaveLength(Object.keys($input.item.json).length); + expect(found).toHaveLength(Object.keys($input.item?.json ?? {}).length); expect(found.map((c) => c.label).every((l) => l.endsWith(']'))); }); }); ["{{ $input.item.json['obj'][| }}", "{{ $json['obj'][| }}"].forEach((expression) => { test(`should return completions for: ${expression}`, () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item.json.obj); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue($input.item?.json.obj); const found = completions(expression); if (!found) throw new Error('Expected to find completions'); - expect(found).toHaveLength(Object.keys($input.item.json.obj).length); + expect(found).toHaveLength(Object.keys($input.item?.json.obj ?? {}).length); expect(found.map((c) => c.label).every((l) => l.endsWith(']'))); }); }); @@ -625,7 +614,6 @@ describe('Resolution-based completions', () => { describe('recommended completions', () => { test('should recommend toDateTime() for {{ "1-Feb-2024".| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce('1-Feb-2024'); expect(completions('{{ "1-Feb-2024".| }}')?.[0]).toEqual( @@ -634,7 +622,6 @@ describe('Resolution-based completions', () => { }); test('should recommend toNumber() for: {{ "5.3".| }}', () => { - // @ts-expect-error Spied function is mistyped vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce('5.3'); const options = completions('{{ "5.3".| }}'); expect(options?.[0]).toEqual( @@ -644,7 +631,6 @@ describe('Resolution-based completions', () => { test('should recommend extractEmail() for: {{ "string with test@n8n.io in it".| }}', () => { vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce( - // @ts-expect-error Spied function is mistyped 'string with test@n8n.io in it', ); const options = completions('{{ "string with test@n8n.io in it".| }}'); @@ -654,10 +640,7 @@ describe('Resolution-based completions', () => { }); test('should recommend extractDomain(), isEmail() for: {{ "test@n8n.io".| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce( - // @ts-expect-error Spied function is mistyped - 'test@n8n.io', - ); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce('test@n8n.io'); const options = completions('{{ "test@n8n.io".| }}'); expect(options?.[0]).toEqual( expect.objectContaining({ label: 'extractDomain()', section: RECOMMENDED_SECTION }), @@ -668,10 +651,7 @@ describe('Resolution-based completions', () => { }); test('should recommend extractDomain(), extractUrlPath() for: {{ "https://n8n.io/pricing".| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce( - // @ts-expect-error Spied function is mistyped - 'https://n8n.io/pricing', - ); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce('https://n8n.io/pricing'); const options = completions('{{ "https://n8n.io/pricing".| }}'); expect(options?.[0]).toEqual( expect.objectContaining({ label: 'extractDomain()', section: RECOMMENDED_SECTION }), @@ -682,10 +662,7 @@ describe('Resolution-based completions', () => { }); test('should recommend round(),floor(),ceil() for: {{ (5.46).| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce( - // @ts-expect-error Spied function is mistyped - 5.46, - ); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce(5.46); const options = completions('{{ (5.46).| }}'); expect(options?.[0]).toEqual( expect.objectContaining({ label: 'round()', section: RECOMMENDED_SECTION }), @@ -699,10 +676,7 @@ describe('Resolution-based completions', () => { }); test("should recommend toDateTime('s') for: {{ (1900062210).| }}", () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce( - // @ts-expect-error Spied function is mistyped - 1900062210, - ); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce(1900062210); const options = completions('{{ (1900062210).| }}'); expect(options?.[0]).toEqual( expect.objectContaining({ label: "toDateTime('s')", section: RECOMMENDED_SECTION }), @@ -710,10 +684,7 @@ describe('Resolution-based completions', () => { }); test("should recommend toDateTime('ms') for: {{ (1900062210000).| }}", () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce( - // @ts-expect-error Spied function is mistyped - 1900062210000, - ); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce(1900062210000); const options = completions('{{ (1900062210000).| }}'); expect(options?.[0]).toEqual( expect.objectContaining({ label: "toDateTime('ms')", section: RECOMMENDED_SECTION }), @@ -721,10 +692,7 @@ describe('Resolution-based completions', () => { }); test('should recommend toBoolean() for: {{ (0).| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce( - // @ts-expect-error Spied function is mistyped - 0, - ); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce(0); const options = completions('{{ (0).| }}'); expect(options?.[0]).toEqual( expect.objectContaining({ label: 'toBoolean()', section: RECOMMENDED_SECTION }), @@ -732,10 +700,7 @@ describe('Resolution-based completions', () => { }); test('should recommend toBoolean() for: {{ "true".| }}', () => { - vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce( - // @ts-expect-error Spied function is mistyped - 'true', - ); + vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValueOnce('true'); const options = completions('{{ "true".| }}'); expect(options?.[0]).toEqual( expect.objectContaining({ label: 'toBoolean()', section: RECOMMENDED_SECTION }), @@ -746,9 +711,7 @@ describe('Resolution-based completions', () => { describe('explicit completions (opened by Ctrl+Space or programatically)', () => { test('should return completions for: {{ $json.foo| }}', () => { vi.spyOn(workflowHelpers, 'resolveParameter') - // @ts-expect-error Spied function is mistyped .mockReturnValueOnce(undefined) - // @ts-expect-error Spied function is mistyped .mockReturnValueOnce('foo'); const result = completions('{{ $json.foo| }}', true); diff --git a/packages/editor-ui/src/plugins/codemirror/completions/bracketAccess.completions.ts b/packages/editor-ui/src/plugins/codemirror/completions/bracketAccess.completions.ts index a922b1354cdf5..6c018ef8e80a6 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/bracketAccess.completions.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/bracketAccess.completions.ts @@ -1,6 +1,5 @@ import { resolveParameter } from '@/composables/useWorkflowHelpers'; import { prefixMatch, longestCommonPrefix } from './utils'; -import type { IDataObject } from 'n8n-workflow'; import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import type { Resolved } from './types'; import { escapeMappingString } from '@/utils/mappingUtils'; @@ -37,7 +36,7 @@ export function bracketAccessCompletions(context: CompletionContext): Completion return null; } - if (resolved === null || resolved === undefined) return null; + if (resolved === null || resolved === undefined || typeof resolved !== 'object') return null; let options = bracketAccessOptions(resolved); @@ -59,7 +58,7 @@ export function bracketAccessCompletions(context: CompletionContext): Completion }; } -function bracketAccessOptions(resolved: IDataObject) { +function bracketAccessOptions(resolved: object) { const SKIP = new Set(['__ob__', 'pairedItem']); return Object.keys(resolved)