From 1b04be1240ec29151e79162680907710c71c6488 Mon Sep 17 00:00:00 2001 From: Elias Meire Date: Tue, 10 Sep 2024 17:26:17 +0200 Subject: [PATCH] feat(editor): Add lint for $('Node').item in runOnceForAllItems mode (#10743) --- cypress/e2e/6-code-node.cy.ts | 45 +++++++++++++++++++ .../src/components/CodeNodeEditor/linter.ts | 21 ++++++--- .../src/styles/plugins/_codemirror.scss | 5 +++ 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/cypress/e2e/6-code-node.cy.ts b/cypress/e2e/6-code-node.cy.ts index 74e775453b82b..43486590c716b 100644 --- a/cypress/e2e/6-code-node.cy.ts +++ b/cypress/e2e/6-code-node.cy.ts @@ -38,6 +38,51 @@ describe('Code node', () => { successToast().contains('Node executed successfully'); }); + + it('should show lint errors in `runOnceForAllItems` mode', () => { + const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible'); + const getEditor = () => getParameter().find('.cm-content').should('exist'); + + getEditor().type('{selectall}').paste(`$input.itemMatching() +$input.item +$('When clicking ‘Test workflow’').item +$input.first(1) + +for (const item of $input.all()) { + item.foo +} + +return +`); + getParameter().get('.cm-lint-marker-error').should('have.length', 6); + getParameter().contains('itemMatching').realHover(); + cy.get('.cm-tooltip-lint').should( + 'have.text', + '`.itemMatching()` expects an item index to be passed in as its argument.', + ); + }); + + it('should show lint errors in `runOnceForEachItem` mode', () => { + const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible'); + const getEditor = () => getParameter().find('.cm-content').should('exist'); + + ndv.getters.parameterInput('mode').click(); + ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item'); + getEditor().type('{selectall}').paste(`$input.itemMatching() +$input.all() +$input.first() +$input.item() + +return [] +`); + + getParameter().get('.cm-lint-marker-error').should('have.length', 5); + getParameter().contains('all').realHover(); + cy.get('.cm-tooltip-lint').should( + 'have.text', + "Method `$input.all()` is only available in the 'Run Once for All Items' mode.", + ); + }); }); describe('Ask AI', () => { diff --git a/packages/editor-ui/src/components/CodeNodeEditor/linter.ts b/packages/editor-ui/src/components/CodeNodeEditor/linter.ts index 254cce2c4193b..bf7615faaffe2 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/linter.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/linter.ts @@ -2,7 +2,7 @@ import type { Diagnostic } from '@codemirror/lint'; import { linter } from '@codemirror/lint'; import type { EditorView } from '@codemirror/view'; import * as esprima from 'esprima-next'; -import type { Node } from 'estree'; +import type { Node, MemberExpression } from 'estree'; import type { CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow'; import { toValue, type MaybeRefOrGetter } from 'vue'; @@ -153,13 +153,20 @@ export const useLinter = ( if (toValue(mode) === 'runOnceForAllItems') { type TargetNode = RangeNode & { property: RangeNode }; + const isInputIdentifier = (node: Node) => + node.type === 'Identifier' && node.name === '$input'; + const isPreviousNodeCall = (node: Node) => + node.type === 'CallExpression' && + node.callee.type === 'Identifier' && + node.callee.name === '$'; + const isDirectMemberExpression = (node: Node): node is MemberExpression => + node.type === 'MemberExpression' && !node.computed; + const isItemIdentifier = (node: Node) => node.type === 'Identifier' && node.name === 'item'; + const isUnavailableInputItemAccess = (node: Node) => - node.type === 'MemberExpression' && - !node.computed && - node.object.type === 'Identifier' && - node.object.name === '$input' && - node.property.type === 'Identifier' && - node.property.name === 'item'; + isDirectMemberExpression(node) && + (isInputIdentifier(node.object) || isPreviousNodeCall(node.object)) && + isItemIdentifier(node.property); walk(ast, isUnavailableInputItemAccess).forEach((node) => { const [start, end] = getRange(node.property); diff --git a/packages/editor-ui/src/styles/plugins/_codemirror.scss b/packages/editor-ui/src/styles/plugins/_codemirror.scss index 060150f527fe6..4eb6382dc525f 100644 --- a/packages/editor-ui/src/styles/plugins/_codemirror.scss +++ b/packages/editor-ui/src/styles/plugins/_codemirror.scss @@ -102,6 +102,11 @@ padding: var(--spacing-xs); } + &:has(.cm-tooltip-lint) { + padding: 0; + overflow: hidden; + } + .autocomplete-info-container { display: flex; flex-direction: column;