From 3a9afa54f89f98d5c8606236ba48c9d68e21e6d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 9 Nov 2022 11:27:29 +0100 Subject: [PATCH 1/8] :zap: Alias legacy refs to new syntax --- packages/nodes-base/nodes/Code/Code.node.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/nodes-base/nodes/Code/Code.node.ts b/packages/nodes-base/nodes/Code/Code.node.ts index 03289952f5249..3369ef216fd6a 100644 --- a/packages/nodes-base/nodes/Code/Code.node.ts +++ b/packages/nodes-base/nodes/Code/Code.node.ts @@ -79,6 +79,7 @@ export class Code implements INodeType { const jsCodeAllItems = this.getNodeParameter('jsCode', 0) as string; const context = getSandboxContext.call(this); + context.items = context.$input.all(); const sandbox = new Sandbox(context, workflowMode, nodeMode); if (workflowMode === 'manual') { @@ -111,6 +112,7 @@ export class Code implements INodeType { const jsCodeEachItem = this.getNodeParameter('jsCode', index) as string; const context = getSandboxContext.call(this, index); + context.item = context.$input.item; const sandbox = new Sandbox(context, workflowMode, nodeMode); if (workflowMode === 'manual') { From 1162e8f19f776984742361538b719a8e669258a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 9 Nov 2022 11:27:37 +0100 Subject: [PATCH 2/8] :blue_book: Adjust types --- packages/nodes-base/nodes/Code/Sandbox.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/nodes-base/nodes/Code/Sandbox.ts b/packages/nodes-base/nodes/Code/Sandbox.ts index e86b93debf99e..1e78c93ffdbc8 100644 --- a/packages/nodes-base/nodes/Code/Sandbox.ts +++ b/packages/nodes-base/nodes/Code/Sandbox.ts @@ -250,16 +250,19 @@ export class Sandbox extends NodeVM { } export function getSandboxContext(this: IExecuteFunctions, index?: number) { - const sandboxContext: Record & { $item: (i: number) => IWorkflowDataProxyData } = - { - // from NodeExecuteFunctions - $getNodeParameter: this.getNodeParameter, - $getWorkflowStaticData: this.getWorkflowStaticData, - helpers: this.helpers, - - // to bring in all $-prefixed vars and methods from WorkflowDataProxy - $item: this.getWorkflowDataProxy, - }; + const sandboxContext: Record & { + $item: (i: number) => IWorkflowDataProxyData; + $input: any; + } = { + // from NodeExecuteFunctions + $getNodeParameter: this.getNodeParameter, + $getWorkflowStaticData: this.getWorkflowStaticData, + helpers: this.helpers, + + // to bring in all $-prefixed vars and methods from WorkflowDataProxy + $item: this.getWorkflowDataProxy, + $input: null, + }; // $node, $items(), $parameter, $json, $env, etc. Object.assign(sandboxContext, sandboxContext.$item(index ?? 0)); From ff130afa8889f70acb753bfb3452725e4aac450c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 9 Nov 2022 11:28:20 +0100 Subject: [PATCH 3/8] :shirt: Switch `item` lint error to warning --- packages/editor-ui/src/components/CodeNodeEditor/linter.ts | 2 +- packages/editor-ui/src/plugins/i18n/locales/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/components/CodeNodeEditor/linter.ts b/packages/editor-ui/src/components/CodeNodeEditor/linter.ts index 74674a612ccb3..443c1bc2f9196 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/linter.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/linter.ts @@ -179,7 +179,7 @@ export const linterExtension = (Vue as CodeNodeEditorMixin).extend({ lintings.push({ from: start, to: end, - severity: DEFAULT_LINTER_SEVERITY, + severity: 'warning', message: this.$locale.baseText('codeNodeEditor.linter.eachItem.legacyItemAccess'), actions: [ { diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 9a6a3c2212a04..072390b583a03 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -236,7 +236,7 @@ "codeNodeEditor.linter.bothModes.varDeclaration.itemProperty": "@:_reusableBaseText.codeNodeEditor.linter.useJson", "codeNodeEditor.linter.bothModes.varDeclaration.itemSubproperty": "@:_reusableBaseText.codeNodeEditor.linter.useJson", "codeNodeEditor.linter.eachItem.emptyReturn": "Code doesn't return an object. Please return an object representing the output item", - "codeNodeEditor.linter.eachItem.legacyItemAccess": "`item` is not defined. Did you mean `$input.item.json`?", + "codeNodeEditor.linter.eachItem.legacyItemAccess": "`item` is a legacy var. Consider using `$input.item.json`", "codeNodeEditor.linter.eachItem.returnArray": "Code doesn't return an object. Array found instead. Please return an object representing the output item", "codeNodeEditor.linter.eachItem.unavailableMethod": "Method `$input.{method}()` is only available in the 'Run Once for All Items' mode.", "codeNodeEditor.linter.bothModes.syntaxError": "Syntax error", From 5e5c63984df1911c812e655aab9b6f06219a69ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 9 Nov 2022 11:28:32 +0100 Subject: [PATCH 4/8] :zap: Add completions for legacy vars --- .../components/CodeNodeEditor/completer.ts | 1 + .../completions/base.completions.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/packages/editor-ui/src/components/CodeNodeEditor/completer.ts b/packages/editor-ui/src/components/CodeNodeEditor/completer.ts index c83c306d61a09..85355cfca13be 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/completer.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/completer.ts @@ -43,6 +43,7 @@ export const completerExtension = mixins( localCompletionSource, // core + this.itemCompletions, this.baseCompletions, this.requireCompletions, this.nodeSelectorCompletions, diff --git a/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts b/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts index 0fd02e4439b1b..f93743b1dd450 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts @@ -20,6 +20,25 @@ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({ ), }, methods: { + itemCompletions(context: CompletionContext): CompletionResult | null { + const preCursor = context.matchBefore(/i/); + + if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null; + + const options: Completion[] = []; + + if (this.mode === 'runOnceForEachItem') { + options.push({ label: 'item', info: 'description....' }); + } else if (this.mode === 'runOnceForAllItems') { + options.push({ label: 'items', info: 'description....' }); + } + + return { + from: preCursor.from, + options, + }; + }, + /** * - Complete `$` to `$execution $input $prevNode $runIndex $workflow $now $today * $jmespath $('nodeName')` in both modes. From 770f88016bb97d5e1f95192e5814bfa2842444de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 9 Nov 2022 13:47:24 +0100 Subject: [PATCH 5/8] :pencil2: Add descriptions to completions --- .../CodeNodeEditor/completions/base.completions.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts b/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts index f93743b1dd450..4a7c1c8c07b89 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts @@ -15,9 +15,7 @@ function getAutocompletableNodeNames(nodes: INodeUi[]) { export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({ computed: { - ...mapStores( - useWorkflowsStore, - ), + ...mapStores(useWorkflowsStore), }, methods: { itemCompletions(context: CompletionContext): CompletionResult | null { @@ -28,9 +26,15 @@ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({ const options: Completion[] = []; if (this.mode === 'runOnceForEachItem') { - options.push({ label: 'item', info: 'description....' }); + options.push({ + label: 'item', + info: this.$locale.baseText('codeNodeEditor.completer.$input.item'), + }); } else if (this.mode === 'runOnceForAllItems') { - options.push({ label: 'items', info: 'description....' }); + options.push({ + label: 'items', + info: this.$locale.baseText('codeNodeEditor.completer.$input.all'), + }); } return { From 6a0abd9f94e77cc6136b08ddb681976dbe49e3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 9 Nov 2022 13:47:52 +0100 Subject: [PATCH 6/8] :zap: Add lintings --- .../src/components/CodeNodeEditor/linter.ts | 75 ++++++++++++++----- .../src/plugins/i18n/locales/en.json | 4 +- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/packages/editor-ui/src/components/CodeNodeEditor/linter.ts b/packages/editor-ui/src/components/CodeNodeEditor/linter.ts index 443c1bc2f9196..0660ee2a8c3f9 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/linter.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/linter.ts @@ -126,19 +126,19 @@ export const linterExtension = (Vue as CodeNodeEditorMixin).extend({ /** * Lint for `.item` unavailable in `runOnceForAllItems` mode * - * $input.all().item -> + * $input.item -> */ if (this.mode === 'runOnceForAllItems') { type TargetNode = RangeNode & { property: RangeNode }; - const isUnavailablePropertyinAllItems = (node: Node) => + const isUnavailableItemAccess = (node: Node) => node.type === 'MemberExpression' && node.computed === false && node.property.type === 'Identifier' && node.property.name === 'item'; - walk(ast, isUnavailablePropertyinAllItems).forEach((node) => { + walk(ast, isUnavailableItemAccess).forEach((node) => { const [start, end] = this.getRange(node.property); lintings.push({ @@ -159,39 +159,74 @@ export const linterExtension = (Vue as CodeNodeEditorMixin).extend({ } /** - * Lint for `item` (legacy var from Function Item node) being accessed - * in `runOnceForEachItem` mode, unless user-defined `item`. + * Lint for `item` (legacy var from Function Item node) unavailable + * in `runOnceForAllItems` mode, unless user-defined `item`. * - * item. -> $input.item.json. + * item -> $input.all() */ - if (this.mode === 'runOnceForEachItem' && !/(let|const|var) item =/.test(script)) { + if (this.mode === 'runOnceForAllItems' && !/(let|const|var) item (=|of)/.test(script)) { type TargetNode = RangeNode & { object: RangeNode & { name: string } }; - const isItemAccess = (node: Node) => - node.type === 'MemberExpression' && - node.computed === false && - node.object.type === 'Identifier' && - node.object.name === 'item'; + const isUnavailableLegacyItems = (node: Node) => + node.type === 'Identifier' && node.name === 'item'; - walk(ast, isItemAccess).forEach((node) => { - const [start, end] = this.getRange(node.object); + walk(ast, isUnavailableLegacyItems).forEach((node) => { + const [start, end] = this.getRange(node); lintings.push({ from: start, to: end, - severity: 'warning', - message: this.$locale.baseText('codeNodeEditor.linter.eachItem.legacyItemAccess'), + severity: DEFAULT_LINTER_SEVERITY, + message: this.$locale.baseText('codeNodeEditor.linter.allItems.unavailableItem'), + actions: [ + { + name: 'Fix', + apply(view, from, to) { + // prevent second insertion of unknown origin + if (view.state.doc.toString().slice(from, to).includes('$input.all()')) { + return; + } + + view.dispatch({ changes: { from: start, to: end } }); + view.dispatch({ changes: { from, insert: '$input.all()' } }); + }, + }, + ], + }); + }); + } + + /** + * Lint for `items` (legacy var from Function node) unavailable + * in `runOnceForEachItem` mode, unless user-defined `items`. + * + * items -> $input.item + */ + if (this.mode === 'runOnceForEachItem' && !/(let|const|var) items =/.test(script)) { + type TargetNode = RangeNode & { object: RangeNode & { name: string } }; + + const isUnavailableLegacyItems = (node: Node) => + node.type === 'Identifier' && node.name === 'items'; + + walk(ast, isUnavailableLegacyItems).forEach((node) => { + const [start, end] = this.getRange(node); + + lintings.push({ + from: start, + to: end, + severity: DEFAULT_LINTER_SEVERITY, + message: this.$locale.baseText('codeNodeEditor.linter.eachItem.unavailableItems'), actions: [ { name: 'Fix', apply(view, from, to) { // prevent second insertion of unknown origin - if (view.state.doc.toString().slice(from, to).includes('$input.item.json')) { + if (view.state.doc.toString().slice(from, to).includes('$input.item')) { return; } view.dispatch({ changes: { from: start, to: end } }); - view.dispatch({ changes: { from, insert: '$input.item.json' } }); + view.dispatch({ changes: { from, insert: '$input.item' } }); }, }, ], @@ -285,8 +320,8 @@ export const linterExtension = (Vue as CodeNodeEditorMixin).extend({ node.callee.object.type === 'Identifier' && node.callee.object.name === '$input' && node.callee.property.type === 'Identifier' && - ['first', 'last'].includes(node.callee.property.name) - && node.arguments.length !== 0; + ['first', 'last'].includes(node.callee.property.name) && + node.arguments.length !== 0; walk(ast, inputFirstOrLastCalledWithArg).forEach((node) => { const [start, end] = this.getRange(node.callee.property); diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 072390b583a03..5df1906214a2e 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -229,6 +229,7 @@ "codeNodeEditor.linter.allItems.emptyReturn": "Code doesn't return items properly. Please return an array of objects, one for each item you would like to output.", "codeNodeEditor.linter.allItems.itemCall": "`item` is a property to access, not a method to call. Did you mean `.item` without brackets?", "codeNodeEditor.linter.allItems.itemMatchingNoArg": "`.itemMatching()` expects an item index to be passed in as its argument.", + "codeNodeEditor.linter.allItems.unavailableItem": "Legacy `item` is only available in the 'Run Once for Each Item' mode.", "codeNodeEditor.linter.allItems.unavailableProperty": "`.item` is only available in the 'Run Once for Each Item' mode.", "codeNodeEditor.linter.allItems.unavailableVar": "is only available in the 'Run Once for Each Item' mode.", "codeNodeEditor.linter.bothModes.directAccess.firstOrLastCall": "@:_reusableBaseText.codeNodeEditor.linter.useJson", @@ -236,8 +237,9 @@ "codeNodeEditor.linter.bothModes.varDeclaration.itemProperty": "@:_reusableBaseText.codeNodeEditor.linter.useJson", "codeNodeEditor.linter.bothModes.varDeclaration.itemSubproperty": "@:_reusableBaseText.codeNodeEditor.linter.useJson", "codeNodeEditor.linter.eachItem.emptyReturn": "Code doesn't return an object. Please return an object representing the output item", - "codeNodeEditor.linter.eachItem.legacyItemAccess": "`item` is a legacy var. Consider using `$input.item.json`", + "codeNodeEditor.linter.eachItem.legacyItemAccess": "`item` is a legacy var. Consider using `$input.item`", "codeNodeEditor.linter.eachItem.returnArray": "Code doesn't return an object. Array found instead. Please return an object representing the output item", + "codeNodeEditor.linter.eachItem.unavailableItems": "Legacy `items` is only available in the 'Run Once for All Items' mode.", "codeNodeEditor.linter.eachItem.unavailableMethod": "Method `$input.{method}()` is only available in the 'Run Once for All Items' mode.", "codeNodeEditor.linter.bothModes.syntaxError": "Syntax error", "collectionParameter.choose": "Choose...", From 3cd093827320f6fbcabe2ef4086b14bc929f69de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 9 Nov 2022 13:50:36 +0100 Subject: [PATCH 7/8] :blue_book: Skip `any` for now --- packages/nodes-base/nodes/Code/Sandbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Code/Sandbox.ts b/packages/nodes-base/nodes/Code/Sandbox.ts index 1e78c93ffdbc8..1ac56e2823982 100644 --- a/packages/nodes-base/nodes/Code/Sandbox.ts +++ b/packages/nodes-base/nodes/Code/Sandbox.ts @@ -252,7 +252,7 @@ export class Sandbox extends NodeVM { export function getSandboxContext(this: IExecuteFunctions, index?: number) { const sandboxContext: Record & { $item: (i: number) => IWorkflowDataProxyData; - $input: any; + $input: any; // tslint:disable-line: no-any } = { // from NodeExecuteFunctions $getNodeParameter: this.getNodeParameter, From 7a24de33dab751d27f014d37d1a86e9e90eea30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Thu, 10 Nov 2022 16:20:33 +0100 Subject: [PATCH 8/8] :zap: Expand regex --- .../components/CodeNodeEditor/completions/base.completions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts b/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts index 4a7c1c8c07b89..284a5594c40a4 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/completions/base.completions.ts @@ -19,7 +19,7 @@ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({ }, methods: { itemCompletions(context: CompletionContext): CompletionResult | null { - const preCursor = context.matchBefore(/i/); + const preCursor = context.matchBefore(/i\w*/); if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null;