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 4e9ab0148e..61683cd9ee 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 @@ -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); }); @@ -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 }; @@ -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.|() }}'); @@ -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.|() }}'); diff --git a/packages/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts b/packages/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts index 1f25ed8f33..72d93075a8 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts @@ -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; @@ -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'). @@ -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( diff --git a/packages/editor-ui/src/plugins/codemirror/completions/nonDollar.completions.ts b/packages/editor-ui/src/plugins/codemirror/completions/nonDollar.completions.ts index 2223025c07..c9757babf0 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/nonDollar.completions.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/nonDollar.completions.ts @@ -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); @@ -30,7 +29,10 @@ export function nonDollarCompletions(context: CompletionContext): CompletionResu { label: 'Math', type: 'keyword', - info: i18n.rootVars.DateTime, + }, + { + label: 'Object', + type: 'keyword', }, ]; diff --git a/packages/editor-ui/src/plugins/i18n/index.ts b/packages/editor-ui/src/plugins/i18n/index.ts index 1655f6d331..d3b3f59082 100644 --- a/packages/editor-ui/src/plugins/i18n/index.ts +++ b/packages/editor-ui/src/plugins/i18n/index.ts @@ -368,6 +368,13 @@ export class I18nClass { '$workflow.name': this.baseText('codeNodeEditor.completer.$workflow.name'), }; + globalObject: Record = { + 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 = { // getters isValid: this.baseText('codeNodeEditor.completer.luxon.instanceMethods.isValid'), diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index a9645dc898..3e80d5d83c 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -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",