mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Add Object
global completions (#5407)
* ✏️ Add i18n info * ⚡ Mount i18n keys * ✏️ Fix typos in tests * ✨ Add `Object` global completion * ✨ Add `Object` global options completions * 🧪 Add tests
This commit is contained in:
parent
d469a98073
commit
d7b3923c2f
|
@ -57,6 +57,15 @@ describe('Top-level completions', () => {
|
||||||
expect(found[0].label).toBe('Math');
|
expect(found[0].label).toBe('Math');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should return Object completion for: {{ O| }}', () => {
|
||||||
|
const found = completions('{{ O| }}');
|
||||||
|
|
||||||
|
if (!found) throw new Error('Expected to find completion');
|
||||||
|
|
||||||
|
expect(found).toHaveLength(1);
|
||||||
|
expect(found[0].label).toBe('Object');
|
||||||
|
});
|
||||||
|
|
||||||
test('should return dollar completions for: {{ $| }}', () => {
|
test('should return dollar completions for: {{ $| }}', () => {
|
||||||
expect(completions('{{ $| }}')).toHaveLength(dollarOptions().length);
|
expect(completions('{{ $| }}')).toHaveLength(dollarOptions().length);
|
||||||
});
|
});
|
||||||
|
@ -130,6 +139,17 @@ describe('Resolution-based completions', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should return completions for Object methods: {{ Object.values({ abc: 123 }).| }}', () => {
|
||||||
|
// @ts-expect-error Spied function is mistyped
|
||||||
|
resolveParameterSpy.mockReturnValueOnce([123]);
|
||||||
|
|
||||||
|
const found = completions('{{ Object.values({ abc: 123 }).| }}');
|
||||||
|
|
||||||
|
if (!found) throw new Error('Expected to find completion');
|
||||||
|
|
||||||
|
expect(found).toHaveLength(natives('array').length + extensions('array').length);
|
||||||
|
});
|
||||||
|
|
||||||
test('should return completions for object literal', () => {
|
test('should return completions for object literal', () => {
|
||||||
const object = { a: 1 };
|
const object = { a: 1 };
|
||||||
|
|
||||||
|
@ -145,7 +165,7 @@ describe('Resolution-based completions', () => {
|
||||||
const resolveParameterSpy = vi.spyOn(workflowHelpers, 'resolveParameter');
|
const resolveParameterSpy = vi.spyOn(workflowHelpers, 'resolveParameter');
|
||||||
const { $input } = mockProxy;
|
const { $input } = mockProxy;
|
||||||
|
|
||||||
test('should return bracket-aware completions for: {{ $input.item.json.str.| }}', () => {
|
test('should return bracket-aware completions for: {{ $input.item.json.str.|() }}', () => {
|
||||||
resolveParameterSpy.mockReturnValue($input.item.json.str);
|
resolveParameterSpy.mockReturnValue($input.item.json.str);
|
||||||
|
|
||||||
const found = completions('{{ $input.item.json.str.|() }}');
|
const found = completions('{{ $input.item.json.str.|() }}');
|
||||||
|
@ -156,7 +176,7 @@ describe('Resolution-based completions', () => {
|
||||||
expect(found.map((c) => c.label).every((l) => !l.endsWith('()')));
|
expect(found.map((c) => c.label).every((l) => !l.endsWith('()')));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return bracket-aware completions for: {{ $input.item.json.num.| }}', () => {
|
test('should return bracket-aware completions for: {{ $input.item.json.num.|() }}', () => {
|
||||||
resolveParameterSpy.mockReturnValue($input.item.json.num);
|
resolveParameterSpy.mockReturnValue($input.item.json.num);
|
||||||
|
|
||||||
const found = completions('{{ $input.item.json.num.|() }}');
|
const found = completions('{{ $input.item.json.num.|() }}');
|
||||||
|
|
|
@ -32,6 +32,8 @@ export function datatypeCompletions(context: CompletionContext): CompletionResul
|
||||||
|
|
||||||
if (base === 'DateTime') {
|
if (base === 'DateTime') {
|
||||||
options = luxonStaticOptions().map(stripExcessParens(context));
|
options = luxonStaticOptions().map(stripExcessParens(context));
|
||||||
|
} else if (base === 'Object') {
|
||||||
|
options = objectGlobalOptions().map(stripExcessParens(context));
|
||||||
} else {
|
} else {
|
||||||
let resolved: Resolved;
|
let resolved: Resolved;
|
||||||
|
|
||||||
|
@ -287,6 +289,24 @@ export const luxonStaticOptions = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods defined on the global `Object`.
|
||||||
|
*/
|
||||||
|
export const objectGlobalOptions = () => {
|
||||||
|
return ['assign', 'entries', 'keys', 'values'].map((key) => {
|
||||||
|
const option: Completion = {
|
||||||
|
label: key + '()',
|
||||||
|
type: 'function',
|
||||||
|
};
|
||||||
|
|
||||||
|
const info = i18n.globalObject[key];
|
||||||
|
|
||||||
|
if (info) option.info = info;
|
||||||
|
|
||||||
|
return option;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const regexes = {
|
const regexes = {
|
||||||
generalRef: /\$[^$]+\.([^{\s])*/, // $input. or $json. or similar ones
|
generalRef: /\$[^$]+\.([^{\s])*/, // $input. or $json. or similar ones
|
||||||
selectorRef: /\$\(['"][\S\s]+['"]\)\.([^{\s])*/, // $('nodeName').
|
selectorRef: /\$\(['"][\S\s]+['"]\)\.([^{\s])*/, // $('nodeName').
|
||||||
|
@ -299,6 +319,7 @@ const regexes = {
|
||||||
|
|
||||||
mathGlobal: /Math\.([^{\s])*/, // Math.
|
mathGlobal: /Math\.([^{\s])*/, // Math.
|
||||||
datetimeGlobal: /DateTime\.[^.}]*/, // DateTime.
|
datetimeGlobal: /DateTime\.[^.}]*/, // DateTime.
|
||||||
|
objectGlobal: /Object\.(\w+\(.*\)\.[^{\s]*)?/, // Object. or Object.method(arg).
|
||||||
};
|
};
|
||||||
|
|
||||||
const DATATYPE_REGEX = new RegExp(
|
const DATATYPE_REGEX = new RegExp(
|
||||||
|
|
|
@ -4,14 +4,13 @@ import { prefixMatch } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completions offered at the initial position for any char other than `$`.
|
* Completions offered at the initial position for any char other than `$`.
|
||||||
*
|
|
||||||
* Currently only `D...` for `DateTime` and `M...` for `Math`
|
|
||||||
*/
|
*/
|
||||||
export function nonDollarCompletions(context: CompletionContext): CompletionResult | null {
|
export function nonDollarCompletions(context: CompletionContext): CompletionResult | null {
|
||||||
const dateTime = /(\s+)D[ateTim]*/;
|
const dateTime = /(\s+)D[ateTim]*/;
|
||||||
const math = /(\s+)M[ath]*/;
|
const math = /(\s+)M[ath]*/;
|
||||||
|
const object = /(\s+)O[bject]*/;
|
||||||
|
|
||||||
const combinedRegex = new RegExp([dateTime.source, math.source].join('|'));
|
const combinedRegex = new RegExp([dateTime.source, math.source, object.source].join('|'));
|
||||||
|
|
||||||
const word = context.matchBefore(combinedRegex);
|
const word = context.matchBefore(combinedRegex);
|
||||||
|
|
||||||
|
@ -30,7 +29,10 @@ export function nonDollarCompletions(context: CompletionContext): CompletionResu
|
||||||
{
|
{
|
||||||
label: 'Math',
|
label: 'Math',
|
||||||
type: 'keyword',
|
type: 'keyword',
|
||||||
info: i18n.rootVars.DateTime,
|
},
|
||||||
|
{
|
||||||
|
label: 'Object',
|
||||||
|
type: 'keyword',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -368,6 +368,13 @@ export class I18nClass {
|
||||||
'$workflow.name': this.baseText('codeNodeEditor.completer.$workflow.name'),
|
'$workflow.name': this.baseText('codeNodeEditor.completer.$workflow.name'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
globalObject: Record<string, string | undefined> = {
|
||||||
|
assign: this.baseText('codeNodeEditor.completer.globalObject.assign'),
|
||||||
|
entries: this.baseText('codeNodeEditor.completer.globalObject.entries'),
|
||||||
|
keys: this.baseText('codeNodeEditor.completer.globalObject.keys'),
|
||||||
|
values: this.baseText('codeNodeEditor.completer.globalObject.values'),
|
||||||
|
};
|
||||||
|
|
||||||
luxonInstance: Record<string, string | undefined> = {
|
luxonInstance: Record<string, string | undefined> = {
|
||||||
// getters
|
// getters
|
||||||
isValid: this.baseText('codeNodeEditor.completer.luxon.instanceMethods.isValid'),
|
isValid: this.baseText('codeNodeEditor.completer.luxon.instanceMethods.isValid'),
|
||||||
|
|
|
@ -135,6 +135,10 @@
|
||||||
"codeNodeEditor.completer.$workflow.id": "The ID of the workflow",
|
"codeNodeEditor.completer.$workflow.id": "The ID of the workflow",
|
||||||
"codeNodeEditor.completer.$workflow.name": "The name of the workflow",
|
"codeNodeEditor.completer.$workflow.name": "The name of the workflow",
|
||||||
"codeNodeEditor.completer.binary": "The item's binary (file) data",
|
"codeNodeEditor.completer.binary": "The item's binary (file) data",
|
||||||
|
"codeNodeEditor.completer.globalObject.assign": "Copy of the object containing all enumerable own properties",
|
||||||
|
"codeNodeEditor.completer.globalObject.entries": "The object's keys and values",
|
||||||
|
"codeNodeEditor.completer.globalObject.keys": "The object's keys",
|
||||||
|
"codeNodeEditor.completer.globalObject.values": "The object's values",
|
||||||
"codeNodeEditor.completer.json": "The item's JSON data. When in doubt, use this",
|
"codeNodeEditor.completer.json": "The item's JSON data. When in doubt, use this",
|
||||||
"codeNodeEditor.completer.luxon.dateTimeStaticMethods.fromFormat": "Create a DateTime from an input string and format string.",
|
"codeNodeEditor.completer.luxon.dateTimeStaticMethods.fromFormat": "Create a DateTime from an input string and format string.",
|
||||||
"codeNodeEditor.completer.luxon.dateTimeStaticMethods.fromHTTP": "Create a DateTime from an HTTP header date",
|
"codeNodeEditor.completer.luxon.dateTimeStaticMethods.fromHTTP": "Create a DateTime from an HTTP header date",
|
||||||
|
|
Loading…
Reference in a new issue