refactor: Fix type errors in completions code (#9439)

This commit is contained in:
Elias Meire 2024-05-17 13:36:34 +02:00 committed by GitHub
parent db1a40635d
commit feba07ba8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 35 additions and 73 deletions

View file

@ -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<T = IDataObject>(
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(

View file

@ -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);

View file

@ -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)