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 32dd764105f..d1d1ca6d600 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 @@ -163,6 +163,15 @@ 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( + natives({ typeName: 'boolean' }).length + extensions({ typeName: 'boolean' }).length, + ); + }); + 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$ "); 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 5fd734de648..62ab12645da 100644 --- a/packages/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts +++ b/packages/editor-ui/src/plugins/codemirror/completions/datatype.completions.ts @@ -847,6 +847,7 @@ const regexes = { numberLiteral: /\((\d+)\.?(\d*)\)\.(.*)/, // (123). or (123.4). singleQuoteStringLiteral: /('.*')\.([^'{\s])*/, // 'abc'. + booleanLiteral: /(true|false)\.([^'{\s])*/, // true. doubleQuoteStringLiteral: /(".*")\.([^"{\s])*/, // "abc". dateLiteral: /\(?new Date\(\(?.*?\)\)?\.(.*)/, // new Date(). or (new Date()). arrayLiteral: /\(?(\[.*\])\)?\.(.*)/, // [1, 2, 3]. diff --git a/packages/workflow/src/Extensions/BooleanExtensions.ts b/packages/workflow/src/Extensions/BooleanExtensions.ts index c275c304c94..ad0e3758471 100644 --- a/packages/workflow/src/Extensions/BooleanExtensions.ts +++ b/packages/workflow/src/Extensions/BooleanExtensions.ts @@ -1,4 +1,4 @@ -import type { ExtensionMap } from './Extensions'; +import type { Extension, ExtensionMap } from './Extensions'; export function toBoolean(value: boolean) { return value; @@ -8,20 +8,25 @@ export function toInt(value: boolean) { return value ? 1 : 0; } -export function toFloat(value: boolean) { - return value ? 1 : 0; -} - export function toDateTime() { return undefined; } -toInt.doc = { - name: 'toInt', - description: 'Converts a boolean to an integer. `false` is 0, `true` is 1.', +const toFloat = toInt; +const toNumber: Extension = toInt.bind({}); + +toNumber.doc = { + name: 'toNumber', + description: + 'Converts true to 1 and false to 0.', + examples: [ + { example: 'true.toNumber()', evaluated: '1' }, + { example: 'false.toNumber()', evaluated: '0' }, + ], section: 'cast', - returnType: 'boolean', - docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/booleans/#boolean-toInt', + returnType: 'number', + docURL: + 'https://docs.n8n.io/code/builtin/data-transformation-functions/booleans/#boolean-toNumber', }; export const booleanExtensions: ExtensionMap = { @@ -30,6 +35,7 @@ export const booleanExtensions: ExtensionMap = { toBoolean, toInt, toFloat, + toNumber, toDateTime, }, }; diff --git a/packages/workflow/src/Extensions/NumberExtensions.ts b/packages/workflow/src/Extensions/NumberExtensions.ts index 58291033cfc..71b86c13734 100644 --- a/packages/workflow/src/Extensions/NumberExtensions.ts +++ b/packages/workflow/src/Extensions/NumberExtensions.ts @@ -36,6 +36,14 @@ function ceil(value: number) { return Math.ceil(value); } +function abs(value: number) { + return Math.abs(value); +} + +function isInteger(value: number) { + return Number.isInteger(value); +} + function round(value: number, extraArgs: number[]) { const [decimalPlaces = 0] = extraArgs; return +value.toFixed(decimalPlaces); @@ -86,28 +94,40 @@ function toDateTime(value: number, extraArgs: [DateTimeFormat]) { ceil.doc = { name: 'ceil', - description: 'Rounds up a number to a whole number.', + description: 'Rounds the number up to the next whole number', + examples: [{ example: '(1.234).ceil()', evaluated: '2' }], returnType: 'number', docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-ceil', }; floor.doc = { name: 'floor', - description: 'Rounds down a number to a whole number.', + description: 'Rounds the number down to the nearest whole number', + examples: [{ example: '(1.234).floor()', evaluated: '1' }], returnType: 'number', docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-floor', }; isEven.doc = { name: 'isEven', - description: 'Returns true if the number is even. Only works on whole numbers.', + description: + "Returns true if the number is even or false if not. Throws an error if the number isn't a whole number.", + examples: [ + { example: '(33).isEven()', evaluated: 'false' }, + { example: '(42).isEven()', evaluated: 'true' }, + ], returnType: 'boolean', docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-isEven', }; isOdd.doc = { name: 'isOdd', - description: 'Returns true if the number is odd. Only works on whole numbers.', + description: + "Returns true if the number is odd or false if not. Throws an error if the number isn't a whole number.", + examples: [ + { example: '(33).isOdd()', evaluated: 'true' }, + { example: '(42).isOdd()', evaluated: 'false' }, + ], returnType: 'boolean', docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-isOdd', }; @@ -115,27 +135,65 @@ isOdd.doc = { format.doc = { name: 'format', description: - 'Returns a formatted string of a number based on the given `LanguageCode` and `FormatOptions`. When no arguments are given, transforms the number in a format like `1.234`.', + 'Returns a formatted string representing the number. Useful for formatting for a specific language or currency. The same as Intl.NumberFormat().', + examples: [ + { example: "(123456.789).format('de-DE')", evaluated: '123.456,789' }, + { + example: "(123456.789).format('de-DE', {'style': 'currency', 'currency': 'EUR'})", + evaluated: '123.456,79 €', + }, + ], returnType: 'string', args: [ - { name: 'locales?', type: 'LanguageCode' }, - { name: 'options?', type: 'FormatOptions' }, + { + name: 'locale', + optional: true, + description: + 'A locale tag for formatting the number, e.g. fr-FR, en-GB, pr-BR', + default: '"en-US"', + type: 'string', + }, + { + name: 'options', + optional: true, + description: + 'Configuration options for number formatting. More info', + type: 'object', + }, ], docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-format', }; round.doc = { name: 'round', - description: - 'Returns the value of a number rounded to the nearest whole number, unless a decimal place is specified. Defaults to 0 decimal places if no argument is given.', + description: 'Rounds the number to the nearest integer (or decimal place)', + examples: [ + { example: '(1.256).round()', evaluated: '1' }, + { example: '(1.256).round(1)', evaluated: '1.3' }, + { example: '(1.256).round(2)', evaluated: '1.26' }, + ], returnType: 'number', - args: [{ name: 'decimalPlaces?', type: 'number' }], + args: [ + { + name: 'decimalPlaces', + optional: true, + description: 'The number of decimal places to round to', + default: '0', + type: 'number', + }, + ], docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-round', }; toBoolean.doc = { name: 'toBoolean', - description: 'Converts a number to a boolean. 0 is `false`, all other numbers are `true`.', + description: + 'Returns false for 0 and true for any other number (including negative numbers).', + examples: [ + { example: '(12).toBoolean()', evaluated: 'true' }, + { example: '(0).toBoolean()', evaluated: 'false' }, + { example: '(-1.3).toBoolean()', evaluated: 'true' }, + ], section: 'cast', returnType: 'boolean', docURL: @@ -145,14 +203,53 @@ toBoolean.doc = { toDateTime.doc = { name: 'toDateTime', description: - "Converts a number to a DateTime. Defaults to milliseconds. Format can be 'ms' (milliseconds), 's' (seconds), 'us' (microseconds) or 'excel' (Excel 1900 format).", + 'Converts a numerical timestamp into a Luxon DateTime. The format of the timestamp must be specified if it\'s not in milliseconds. Uses the timezone specified in workflow settings if available; otherwise, it defaults to the timezone set for the instance.', + examples: [ + { example: "(1708695471).toDateTime('s')", evaluated: '2024-02-23T14:37:51.000+01:00' }, + { example: "(1708695471000).toDateTime('ms')", evaluated: '2024-02-23T14:37:51.000+01:00' }, + { example: "(1708695471000000).toDateTime('us')", evaluated: '2024-02-23T14:37:51.000+01:00' }, + { example: "(45345).toDateTime('excel')", evaluated: '2024-02-23T01:00:00.000+01:00' }, + ], section: 'cast', returnType: 'DateTime', - args: [{ name: 'format?', type: 'string' }], + args: [ + { + name: 'format', + optional: true, + description: + 'The type of timestamp to convert. Options are ms (for Unix timestamp in milliseconds), s (for Unix timestamp in seconds), us (for Unix timestamp in microseconds) or excel (for days since 1900).', + default: '"ms"', + type: 'string', + }, + ], docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-toDateTime', }; +abs.doc = { + name: 'abs', + description: "Returns the number's absolute value, i.e. removes any minus sign", + examples: [ + { example: '(-1.7).abs()', evaluated: '1.7' }, + { example: '(1.7).abs()', evaluated: '1.7' }, + ], + returnType: 'number', + docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-abs', +}; + +isInteger.doc = { + name: 'isInteger', + description: 'Returns true if the number is a whole number', + examples: [ + { example: '(4).isInteger()', evaluated: 'true' }, + { example: '(4.12).isInteger()', evaluated: 'false' }, + { example: '(-4).isInteger()', evaluated: 'true' }, + ], + returnType: 'boolean', + docURL: + 'https://docs.n8n.io/code/builtin/data-transformation-functions/numbers/#number-isInteger', +}; + export const numberExtensions: ExtensionMap = { typeName: 'Number', functions: { @@ -160,6 +257,8 @@ export const numberExtensions: ExtensionMap = { floor, format, round, + abs, + isInteger, isEven, isOdd, toBoolean, diff --git a/packages/workflow/src/Extensions/StringExtensions.ts b/packages/workflow/src/Extensions/StringExtensions.ts index 9ee944758d3..17e5422c02c 100644 --- a/packages/workflow/src/Extensions/StringExtensions.ts +++ b/packages/workflow/src/Extensions/StringExtensions.ts @@ -445,7 +445,7 @@ toDate.doc = { toDateTime.doc = { name: 'toDateTime', description: - 'Converts the string to a DateTime. Useful for further transformation. Supported formats for the string are ISO 8601, HTTP, RFC2822, SQL and Unix timestamp in milliseconds. To parse other formats, use DateTime.fromFormat().', + 'Converts the string to a Luxon DateTime. Useful for further transformation. Supported formats for the string are ISO 8601, HTTP, RFC2822, SQL and Unix timestamp in milliseconds. To parse other formats, use DateTime.fromFormat().', section: 'cast', returnType: 'DateTime', docURL: diff --git a/packages/workflow/src/NativeMethods/Boolean.methods.ts b/packages/workflow/src/NativeMethods/Boolean.methods.ts index a6c16ae102c..a897f5dc62f 100644 --- a/packages/workflow/src/NativeMethods/Boolean.methods.ts +++ b/packages/workflow/src/NativeMethods/Boolean.methods.ts @@ -6,7 +6,12 @@ export const booleanMethods: NativeDoc = { toString: { doc: { name: 'toString', - description: 'returns a string representing this boolean value.', + description: + "Converts true to the string 'true' and false to the string 'false'.", + examples: [ + { example: 'true.toString()', evaluated: "'true'" }, + { example: 'false.toString()', evaluated: "'false'" }, + ], docURL: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString', returnType: 'string', diff --git a/packages/workflow/src/NativeMethods/Number.methods.ts b/packages/workflow/src/NativeMethods/Number.methods.ts index 13b418962de..751d68fa7cf 100644 --- a/packages/workflow/src/NativeMethods/Number.methods.ts +++ b/packages/workflow/src/NativeMethods/Number.methods.ts @@ -29,18 +29,63 @@ export const numberMethods: NativeDoc = { toString: { doc: { name: 'toString', - description: 'Returns a string representing this number value.', + description: + 'Converts the number to a string. For more formatting options, see toLocaleString().', + examples: [ + { example: '(2).toString()', evaluated: "'2'" }, + { example: '(50.125).toString()', evaluated: "'50.125'" }, + { example: '(5).toString(2)', evaluated: "'101'" }, + { example: '(412).toString(16)', evaluated: "'19c'" }, + ], docURL: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString', + args: [ + { + name: 'base', + optional: true, + description: + 'The base to use. Must be an integer between 2 and 36. E.g. base 2 is binary and base 16 is hexadecimal.', + default: '10', + type: 'number', + }, + ], returnType: 'string', }, }, toLocaleString: { doc: { name: 'toLocaleString', - description: 'Returns a string with a language-sensitive representation of this number.', + description: + "Returns a localized string representing the number, i.e. in the language and format corresponding to its locale. Defaults to the system's locale if none specified.", + examples: [ + { + example: '(500000.125).toLocaleString()', + evaluated: "'500,000.125' (if in US English locale)", + }, + { example: "(500000.125).toLocaleString('fr-FR')", evaluated: "'500 000,125'" }, + { + example: "(500000.125).toLocaleString('fr-FR', {style:'currency', currency:'EUR'})", + evaluated: "'500 000,13 €'", + }, + ], docURL: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString', + args: [ + { + name: 'locale(s)', + optional: true, + description: + 'The locale to use, e.g. \'en-GB\' for British English or \'pt-BR\' for Brazilian Portuguese. See full list (unofficial). Also accepts an array of locales. Defaults to the system locale if not specified.', + type: 'string | string[]', + }, + { + name: 'options', + optional: true, + description: + 'An object with formatting options', + type: 'object', + }, + ], returnType: 'string', }, },