Skip to content

Commit

Permalink
feat(editor): Add Object global completions (#5407)
Browse files Browse the repository at this point in the history
* ✏️ Add i18n info

* ⚡ Mount i18n keys

* ✏️ Fix typos in tests

* ✨ Add `Object` global completion

* ✨ Add `Object` global options completions

* 🧪 Add tests
  • Loading branch information
ivov authored Feb 8, 2023
1 parent d469a98 commit d7b3923
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ describe('Top-level completions', () => {
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: {{ $| }}', () => {
expect(completions('{{ $| }}')).toHaveLength(dollarOptions().length);
});
Expand Down Expand Up @@ -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', () => {
const object = { a: 1 };

Expand All @@ -145,7 +165,7 @@ describe('Resolution-based completions', () => {
const resolveParameterSpy = vi.spyOn(workflowHelpers, 'resolveParameter');
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);

const found = completions('{{ $input.item.json.str.|() }}');
Expand All @@ -156,7 +176,7 @@ describe('Resolution-based completions', () => {
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);

const found = completions('{{ $input.item.json.num.|() }}');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export function datatypeCompletions(context: CompletionContext): CompletionResul

if (base === 'DateTime') {
options = luxonStaticOptions().map(stripExcessParens(context));
} else if (base === 'Object') {
options = objectGlobalOptions().map(stripExcessParens(context));
} else {
let resolved: Resolved;

Expand Down Expand Up @@ -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 = {
generalRef: /\$[^$]+\.([^{\s])*/, // $input. or $json. or similar ones
selectorRef: /\$\(['"][\S\s]+['"]\)\.([^{\s])*/, // $('nodeName').
Expand All @@ -299,6 +319,7 @@ const regexes = {

mathGlobal: /Math\.([^{\s])*/, // Math.
datetimeGlobal: /DateTime\.[^.}]*/, // DateTime.
objectGlobal: /Object\.(\w+\(.*\)\.[^{\s]*)?/, // Object. or Object.method(arg).
};

const DATATYPE_REGEX = new RegExp(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import { prefixMatch } from './utils';

/**
* 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 {
const dateTime = /(\s+)D[ateTim]*/;
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);

Expand All @@ -30,7 +29,10 @@ export function nonDollarCompletions(context: CompletionContext): CompletionResu
{
label: 'Math',
type: 'keyword',
info: i18n.rootVars.DateTime,
},
{
label: 'Object',
type: 'keyword',
},
];

Expand Down
7 changes: 7 additions & 0 deletions packages/editor-ui/src/plugins/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,13 @@ export class I18nClass {
'$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> = {
// getters
isValid: this.baseText('codeNodeEditor.completer.luxon.instanceMethods.isValid'),
Expand Down
4 changes: 4 additions & 0 deletions packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@
"codeNodeEditor.completer.$workflow.id": "The ID of the workflow",
"codeNodeEditor.completer.$workflow.name": "The name of the workflow",
"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.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",
Expand Down

0 comments on commit d7b3923

Please sign in to comment.