mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 21:07:28 -08:00
feat(editor): Add lint for $('Node').item in runOnceForAllItems mode (#10743)
This commit is contained in:
parent
6ecf84ffe8
commit
1b04be1240
|
@ -38,6 +38,51 @@ describe('Code node', () => {
|
||||||
|
|
||||||
successToast().contains('Node executed successfully');
|
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', () => {
|
describe('Ask AI', () => {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Diagnostic } from '@codemirror/lint';
|
||||||
import { linter } from '@codemirror/lint';
|
import { linter } from '@codemirror/lint';
|
||||||
import type { EditorView } from '@codemirror/view';
|
import type { EditorView } from '@codemirror/view';
|
||||||
import * as esprima from 'esprima-next';
|
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 type { CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow';
|
||||||
import { toValue, type MaybeRefOrGetter } from 'vue';
|
import { toValue, type MaybeRefOrGetter } from 'vue';
|
||||||
|
|
||||||
|
@ -153,13 +153,20 @@ export const useLinter = (
|
||||||
if (toValue(mode) === 'runOnceForAllItems') {
|
if (toValue(mode) === 'runOnceForAllItems') {
|
||||||
type TargetNode = RangeNode & { property: RangeNode };
|
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) =>
|
const isUnavailableInputItemAccess = (node: Node) =>
|
||||||
node.type === 'MemberExpression' &&
|
isDirectMemberExpression(node) &&
|
||||||
!node.computed &&
|
(isInputIdentifier(node.object) || isPreviousNodeCall(node.object)) &&
|
||||||
node.object.type === 'Identifier' &&
|
isItemIdentifier(node.property);
|
||||||
node.object.name === '$input' &&
|
|
||||||
node.property.type === 'Identifier' &&
|
|
||||||
node.property.name === 'item';
|
|
||||||
|
|
||||||
walk<TargetNode>(ast, isUnavailableInputItemAccess).forEach((node) => {
|
walk<TargetNode>(ast, isUnavailableInputItemAccess).forEach((node) => {
|
||||||
const [start, end] = getRange(node.property);
|
const [start, end] = getRange(node.property);
|
||||||
|
|
|
@ -102,6 +102,11 @@
|
||||||
padding: var(--spacing-xs);
|
padding: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:has(.cm-tooltip-lint) {
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.autocomplete-info-container {
|
.autocomplete-info-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
Loading…
Reference in a new issue