@n8n/eslint-plugin-community-nodes 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/README.md +39 -52
- package/dist/plugin.d.ts +0 -78
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +0 -26
- package/dist/plugin.js.map +1 -1
- package/dist/rules/index.d.ts +0 -13
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +0 -26
- package/dist/rules/index.js.map +1 -1
- package/dist/typecheck.tsbuildinfo +1 -1
- package/docs/rules/no-forbidden-lifecycle-scripts.md +1 -1
- package/package.json +4 -4
- package/src/plugin.ts +0 -26
- package/src/rules/index.ts +0 -26
- package/dist/rules/cred-class-name-field-conventions.d.ts +0 -2
- package/dist/rules/cred-class-name-field-conventions.d.ts.map +0 -1
- package/dist/rules/cred-class-name-field-conventions.js +0 -89
- package/dist/rules/cred-class-name-field-conventions.js.map +0 -1
- package/dist/rules/cred-class-name-suffix.d.ts +0 -2
- package/dist/rules/cred-class-name-suffix.d.ts.map +0 -1
- package/dist/rules/cred-class-name-suffix.js +0 -53
- package/dist/rules/cred-class-name-suffix.js.map +0 -1
- package/dist/rules/cred-filename-against-convention.d.ts +0 -2
- package/dist/rules/cred-filename-against-convention.d.ts.map +0 -1
- package/dist/rules/cred-filename-against-convention.js +0 -42
- package/dist/rules/cred-filename-against-convention.js.map +0 -1
- package/dist/rules/no-asterisk-in-option-names.d.ts +0 -2
- package/dist/rules/no-asterisk-in-option-names.d.ts.map +0 -1
- package/dist/rules/no-asterisk-in-option-names.js +0 -90
- package/dist/rules/no-asterisk-in-option-names.js.map +0 -1
- package/dist/rules/no-duplicate-param-options.d.ts +0 -2
- package/dist/rules/no-duplicate-param-options.d.ts.map +0 -1
- package/dist/rules/no-duplicate-param-options.js +0 -97
- package/dist/rules/no-duplicate-param-options.js.map +0 -1
- package/dist/rules/node-class-description-name-camelcase.d.ts +0 -2
- package/dist/rules/node-class-description-name-camelcase.d.ts.map +0 -1
- package/dist/rules/node-class-description-name-camelcase.js +0 -92
- package/dist/rules/node-class-description-name-camelcase.js.map +0 -1
- package/dist/rules/node-filename-against-convention.d.ts +0 -2
- package/dist/rules/node-filename-against-convention.d.ts.map +0 -1
- package/dist/rules/node-filename-against-convention.js +0 -61
- package/dist/rules/node-filename-against-convention.js.map +0 -1
- package/dist/rules/node-operation-error-itemindex.d.ts +0 -12
- package/dist/rules/node-operation-error-itemindex.d.ts.map +0 -1
- package/dist/rules/node-operation-error-itemindex.js +0 -184
- package/dist/rules/node-operation-error-itemindex.js.map +0 -1
- package/dist/rules/options-sorted-alphabetically.d.ts +0 -2
- package/dist/rules/options-sorted-alphabetically.d.ts.map +0 -1
- package/dist/rules/options-sorted-alphabetically.js +0 -95
- package/dist/rules/options-sorted-alphabetically.js.map +0 -1
- package/dist/rules/require-community-node-keyword.d.ts +0 -2
- package/dist/rules/require-community-node-keyword.d.ts.map +0 -1
- package/dist/rules/require-community-node-keyword.js +0 -65
- package/dist/rules/require-community-node-keyword.js.map +0 -1
- package/dist/rules/require-continue-on-fail.d.ts +0 -2
- package/dist/rules/require-continue-on-fail.d.ts.map +0 -1
- package/dist/rules/require-continue-on-fail.js +0 -76
- package/dist/rules/require-continue-on-fail.js.map +0 -1
- package/dist/rules/require-mit-license.d.ts +0 -2
- package/dist/rules/require-mit-license.d.ts.map +0 -1
- package/dist/rules/require-mit-license.js +0 -57
- package/dist/rules/require-mit-license.js.map +0 -1
- package/dist/rules/require-param-default.d.ts +0 -15
- package/dist/rules/require-param-default.d.ts.map +0 -1
- package/dist/rules/require-param-default.js +0 -75
- package/dist/rules/require-param-default.js.map +0 -1
- package/docs/rules/cred-class-name-field-conventions.md +0 -38
- package/docs/rules/cred-class-name-suffix.md +0 -46
- package/docs/rules/cred-filename-against-convention.md +0 -42
- package/docs/rules/no-asterisk-in-option-names.md +0 -61
- package/docs/rules/no-duplicate-param-options.md +0 -46
- package/docs/rules/node-class-description-name-camelcase.md +0 -41
- package/docs/rules/node-filename-against-convention.md +0 -50
- package/docs/rules/node-operation-error-itemindex.md +0 -81
- package/docs/rules/options-sorted-alphabetically.md +0 -63
- package/docs/rules/require-community-node-keyword.md +0 -45
- package/docs/rules/require-continue-on-fail.md +0 -56
- package/docs/rules/require-mit-license.md +0 -38
- package/docs/rules/require-param-default.md +0 -60
- package/src/rules/cred-class-name-field-conventions.test.ts +0 -121
- package/src/rules/cred-class-name-field-conventions.ts +0 -102
- package/src/rules/cred-class-name-suffix.test.ts +0 -74
- package/src/rules/cred-class-name-suffix.ts +0 -57
- package/src/rules/cred-filename-against-convention.test.ts +0 -72
- package/src/rules/cred-filename-against-convention.ts +0 -48
- package/src/rules/no-asterisk-in-option-names.test.ts +0 -185
- package/src/rules/no-asterisk-in-option-names.ts +0 -111
- package/src/rules/no-duplicate-param-options.test.ts +0 -125
- package/src/rules/no-duplicate-param-options.ts +0 -125
- package/src/rules/node-class-description-name-camelcase.test.ts +0 -121
- package/src/rules/node-class-description-name-camelcase.ts +0 -114
- package/src/rules/node-filename-against-convention.test.ts +0 -115
- package/src/rules/node-filename-against-convention.ts +0 -76
- package/src/rules/node-operation-error-itemindex.test.ts +0 -280
- package/src/rules/node-operation-error-itemindex.ts +0 -223
- package/src/rules/options-sorted-alphabetically.test.ts +0 -468
- package/src/rules/options-sorted-alphabetically.ts +0 -129
- package/src/rules/require-community-node-keyword.test.ts +0 -82
- package/src/rules/require-community-node-keyword.ts +0 -78
- package/src/rules/require-continue-on-fail.test.ts +0 -129
- package/src/rules/require-continue-on-fail.ts +0 -88
- package/src/rules/require-mit-license.test.ts +0 -67
- package/src/rules/require-mit-license.ts +0 -65
- package/src/rules/require-param-default.test.ts +0 -227
- package/src/rules/require-param-default.ts +0 -89
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
isNodeTypeClass,
|
|
5
|
-
findClassProperty,
|
|
6
|
-
findObjectProperty,
|
|
7
|
-
getStringLiteralValue,
|
|
8
|
-
isFileType,
|
|
9
|
-
createRule,
|
|
10
|
-
} from '../utils/index.js';
|
|
11
|
-
|
|
12
|
-
// `description.name` must be camelCase: a lowercase letter followed by
|
|
13
|
-
// letters/digits, with no separators.
|
|
14
|
-
const CAMEL_CASE_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Converts an arbitrary node name to camelCase by splitting on any
|
|
18
|
-
* non-alphanumeric separators, upper-casing the first character of each
|
|
19
|
-
* subsequent segment, and lower-casing the very first character.
|
|
20
|
-
* Examples: `My Node` -> `myNode`, `my-node` -> `myNode`, `GitHub` -> `gitHub`.
|
|
21
|
-
*/
|
|
22
|
-
function toCamelCase(value: string): string {
|
|
23
|
-
const segments = value.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
24
|
-
if (segments.length === 0) {
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const joined = segments
|
|
29
|
-
.map((segment, index) =>
|
|
30
|
-
index === 0 ? segment : segment.charAt(0).toUpperCase() + segment.slice(1),
|
|
31
|
-
)
|
|
32
|
-
.join('');
|
|
33
|
-
|
|
34
|
-
return joined.charAt(0).toLowerCase() + joined.slice(1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Serialize a value as a string literal using the original quote character,
|
|
38
|
-
// escaping any characters that would otherwise break the literal so the
|
|
39
|
-
// autofix never emits invalid code.
|
|
40
|
-
function toStringLiteral(value: string, quote: string): string {
|
|
41
|
-
const escaped = value
|
|
42
|
-
.replace(/\\/g, '\\\\')
|
|
43
|
-
.replace(/\n/g, '\\n')
|
|
44
|
-
.replace(/\r/g, '\\r')
|
|
45
|
-
.split(quote)
|
|
46
|
-
.join(`\\${quote}`);
|
|
47
|
-
return `${quote}${escaped}${quote}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const NodeClassDescriptionNameCamelCaseRule = createRule({
|
|
51
|
-
name: 'node-class-description-name-camelcase',
|
|
52
|
-
meta: {
|
|
53
|
-
type: 'problem',
|
|
54
|
-
docs: {
|
|
55
|
-
description: 'Node class `description.name` must be camelCase',
|
|
56
|
-
},
|
|
57
|
-
messages: {
|
|
58
|
-
notCamelCase: "Node `description.name` '{{value}}' must be camelCase",
|
|
59
|
-
},
|
|
60
|
-
fixable: 'code',
|
|
61
|
-
schema: [],
|
|
62
|
-
},
|
|
63
|
-
defaultOptions: [],
|
|
64
|
-
create(context) {
|
|
65
|
-
if (!isFileType(context.filename, '.node.ts')) {
|
|
66
|
-
return {};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
ClassDeclaration(node) {
|
|
71
|
-
if (!isNodeTypeClass(node)) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const descriptionProperty = findClassProperty(node, 'description');
|
|
76
|
-
if (descriptionProperty?.value?.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const nameProperty = findObjectProperty(descriptionProperty.value, 'name');
|
|
81
|
-
if (!nameProperty) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const nameValue = getStringLiteralValue(nameProperty.value);
|
|
86
|
-
if (nameValue === null) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (CAMEL_CASE_PATTERN.test(nameValue)) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const valueNode = nameProperty.value;
|
|
95
|
-
const fixedValue = toCamelCase(nameValue);
|
|
96
|
-
// Only offer an autofix when it actually yields a valid camelCase
|
|
97
|
-
// value (e.g. names starting with a digit cannot be repaired).
|
|
98
|
-
const canFix = fixedValue !== nameValue && CAMEL_CASE_PATTERN.test(fixedValue);
|
|
99
|
-
|
|
100
|
-
context.report({
|
|
101
|
-
node: valueNode,
|
|
102
|
-
messageId: 'notCamelCase',
|
|
103
|
-
data: { value: nameValue },
|
|
104
|
-
fix: canFix
|
|
105
|
-
? (fixer) => {
|
|
106
|
-
const quote = context.sourceCode.getText(valueNode).charAt(0);
|
|
107
|
-
return fixer.replaceText(valueNode, toStringLiteral(fixedValue, quote));
|
|
108
|
-
}
|
|
109
|
-
: undefined,
|
|
110
|
-
});
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
},
|
|
114
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
-
|
|
3
|
-
import { NodeFilenameAgainstConventionRule } from './node-filename-against-convention.js';
|
|
4
|
-
|
|
5
|
-
const ruleTester = new RuleTester();
|
|
6
|
-
|
|
7
|
-
function createNodeCode(name: string): string {
|
|
8
|
-
return `
|
|
9
|
-
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
10
|
-
|
|
11
|
-
export class TestNode implements INodeType {
|
|
12
|
-
description: INodeTypeDescription = {
|
|
13
|
-
displayName: 'Test Node',
|
|
14
|
-
name: '${name}',
|
|
15
|
-
group: ['input'],
|
|
16
|
-
version: 1,
|
|
17
|
-
description: 'A test node',
|
|
18
|
-
defaults: { name: 'Test Node' },
|
|
19
|
-
inputs: [],
|
|
20
|
-
outputs: [],
|
|
21
|
-
properties: [],
|
|
22
|
-
};
|
|
23
|
-
}`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function createNodeCodeWithoutName(): string {
|
|
27
|
-
return `
|
|
28
|
-
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
29
|
-
|
|
30
|
-
export class TestNode implements INodeType {
|
|
31
|
-
description: INodeTypeDescription = {
|
|
32
|
-
displayName: 'Test Node',
|
|
33
|
-
group: ['input'],
|
|
34
|
-
version: 1,
|
|
35
|
-
description: 'A test node',
|
|
36
|
-
defaults: { name: 'Test Node' },
|
|
37
|
-
inputs: [],
|
|
38
|
-
outputs: [],
|
|
39
|
-
properties: [],
|
|
40
|
-
};
|
|
41
|
-
}`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function createRegularClass(): string {
|
|
45
|
-
return `
|
|
46
|
-
export class SomeHelper {
|
|
47
|
-
name = 'github';
|
|
48
|
-
}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
ruleTester.run('node-filename-against-convention', NodeFilenameAgainstConventionRule, {
|
|
52
|
-
valid: [
|
|
53
|
-
{
|
|
54
|
-
name: 'filename matches PascalCased description.name',
|
|
55
|
-
filename: '/tmp/Github.node.ts',
|
|
56
|
-
code: createNodeCode('github'),
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'filename with version suffix is accepted',
|
|
60
|
-
filename: '/tmp/GithubV2.node.ts',
|
|
61
|
-
code: createNodeCode('github'),
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
name: 'filename with multi-digit version suffix is accepted',
|
|
65
|
-
filename: '/tmp/GithubV10.node.ts',
|
|
66
|
-
code: createNodeCode('github'),
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: 'multi-word camelCase name maps to PascalCase filename',
|
|
70
|
-
filename: '/tmp/GoogleSheets.node.ts',
|
|
71
|
-
code: createNodeCode('googleSheets'),
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
name: 'class not implementing INodeType is ignored',
|
|
75
|
-
filename: '/tmp/Github.node.ts',
|
|
76
|
-
code: createRegularClass(),
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: 'non-.node.ts file is ignored',
|
|
80
|
-
filename: '/tmp/Github.ts',
|
|
81
|
-
code: createNodeCode('mismatch'),
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: 'missing description.name is ignored',
|
|
85
|
-
filename: '/tmp/Github.node.ts',
|
|
86
|
-
code: createNodeCodeWithoutName(),
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
|
-
invalid: [
|
|
90
|
-
{
|
|
91
|
-
name: 'filename does not match description.name',
|
|
92
|
-
filename: '/tmp/Github.node.ts',
|
|
93
|
-
code: createNodeCode('gitlab'),
|
|
94
|
-
errors: [{ messageId: 'renameFile', data: { expected: 'Gitlab.node.ts' } }],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: 'filename is lowercased',
|
|
98
|
-
filename: '/tmp/github.node.ts',
|
|
99
|
-
code: createNodeCode('github'),
|
|
100
|
-
errors: [{ messageId: 'renameFile', data: { expected: 'Github.node.ts' } }],
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: 'filename has wrong internal casing',
|
|
104
|
-
filename: '/tmp/GitHub.node.ts',
|
|
105
|
-
code: createNodeCode('github'),
|
|
106
|
-
errors: [{ messageId: 'renameFile', data: { expected: 'Github.node.ts' } }],
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: 'version suffix does not excuse a mismatched base name',
|
|
110
|
-
filename: '/tmp/GitlabV2.node.ts',
|
|
111
|
-
code: createNodeCode('github'),
|
|
112
|
-
errors: [{ messageId: 'renameFile', data: { expected: 'Github.node.ts' } }],
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
});
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
isNodeTypeClass,
|
|
6
|
-
findClassProperty,
|
|
7
|
-
findObjectProperty,
|
|
8
|
-
getStringLiteralValue,
|
|
9
|
-
isFileType,
|
|
10
|
-
createRule,
|
|
11
|
-
} from '../utils/index.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Converts a `description.name` to the expected node file basename by
|
|
15
|
-
* upper-casing the first character. The source of truth is `description.name`,
|
|
16
|
-
* not the class name. Example: `github` -> `Github`.
|
|
17
|
-
*/
|
|
18
|
-
function toExpectedBaseName(name: string): string {
|
|
19
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const NodeFilenameAgainstConventionRule = createRule({
|
|
23
|
-
name: 'node-filename-against-convention',
|
|
24
|
-
meta: {
|
|
25
|
-
type: 'problem',
|
|
26
|
-
docs: {
|
|
27
|
-
description: 'Node filename must match the node `description.name`',
|
|
28
|
-
},
|
|
29
|
-
messages: {
|
|
30
|
-
renameFile: 'Node filename must match `description.name`. Rename file to "{{expected}}".',
|
|
31
|
-
},
|
|
32
|
-
schema: [],
|
|
33
|
-
},
|
|
34
|
-
defaultOptions: [],
|
|
35
|
-
create(context) {
|
|
36
|
-
if (!isFileType(context.filename, '.node.ts')) {
|
|
37
|
-
return {};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
ClassDeclaration(node) {
|
|
42
|
-
if (!isNodeTypeClass(node)) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const descriptionProperty = findClassProperty(node, 'description');
|
|
47
|
-
if (descriptionProperty?.value?.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const nameProperty = findObjectProperty(descriptionProperty.value, 'name');
|
|
52
|
-
if (!nameProperty) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const name = getStringLiteralValue(nameProperty.value);
|
|
57
|
-
if (!name) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const expectedBaseName = toExpectedBaseName(name);
|
|
62
|
-
// Strip the `.node.ts` extension and any trailing `V<digits>` version
|
|
63
|
-
// suffix so versioned files (e.g. `GithubV2.node.ts`) are accepted.
|
|
64
|
-
const actualBaseName = path.basename(context.filename, '.node.ts').replace(/V\d+$/, '');
|
|
65
|
-
|
|
66
|
-
if (actualBaseName !== expectedBaseName) {
|
|
67
|
-
context.report({
|
|
68
|
-
node: nameProperty.value,
|
|
69
|
-
messageId: 'renameFile',
|
|
70
|
-
data: { expected: `${expectedBaseName}.node.ts` },
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
|
-
});
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
-
|
|
3
|
-
import { NodeOperationErrorItemIndexRule } from './node-operation-error-itemindex.js';
|
|
4
|
-
|
|
5
|
-
const ruleTester = new RuleTester();
|
|
6
|
-
|
|
7
|
-
const NODE_FILENAME = 'TestNode.node.ts';
|
|
8
|
-
|
|
9
|
-
function createNodeWithExecute(executeBody: string): { filename: string; code: string } {
|
|
10
|
-
return {
|
|
11
|
-
filename: NODE_FILENAME,
|
|
12
|
-
code: `
|
|
13
|
-
import type { INodeType, INodeTypeDescription, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
|
14
|
-
import { NodeOperationError, NodeApiError } from 'n8n-workflow';
|
|
15
|
-
|
|
16
|
-
export class TestNode implements INodeType {
|
|
17
|
-
description: INodeTypeDescription = {
|
|
18
|
-
displayName: 'Test Node',
|
|
19
|
-
name: 'testNode',
|
|
20
|
-
group: ['input'],
|
|
21
|
-
version: 1,
|
|
22
|
-
description: 'A test node',
|
|
23
|
-
defaults: { name: 'Test Node' },
|
|
24
|
-
inputs: ['main'],
|
|
25
|
-
outputs: ['main'],
|
|
26
|
-
properties: [],
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
30
|
-
${executeBody}
|
|
31
|
-
}
|
|
32
|
-
}`,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
ruleTester.run('node-operation-error-itemindex', NodeOperationErrorItemIndexRule, {
|
|
37
|
-
valid: [
|
|
38
|
-
{
|
|
39
|
-
name: 'non-node class is ignored',
|
|
40
|
-
filename: NODE_FILENAME,
|
|
41
|
-
code: `
|
|
42
|
-
export class RegularClass {
|
|
43
|
-
async execute() {
|
|
44
|
-
const items = this.getInputData();
|
|
45
|
-
for (let i = 0; i < items.length; i++) {
|
|
46
|
-
throw new NodeOperationError(this.getNode(), 'error');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}`,
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: 'NodeOperationError outside any loop is allowed',
|
|
53
|
-
...createNodeWithExecute(`
|
|
54
|
-
throw new NodeOperationError(this.getNode(), 'some error');
|
|
55
|
-
`),
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: 'NodeOperationError in a non-item loop is allowed',
|
|
59
|
-
...createNodeWithExecute(`
|
|
60
|
-
const settings = ['a', 'b', 'c'];
|
|
61
|
-
for (let i = 0; i < settings.length; i++) {
|
|
62
|
-
throw new NodeOperationError(this.getNode(), 'error');
|
|
63
|
-
}
|
|
64
|
-
`),
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: 'NodeOperationError with itemIndex in C-style for loop',
|
|
68
|
-
...createNodeWithExecute(`
|
|
69
|
-
const items = this.getInputData();
|
|
70
|
-
for (let i = 0; i < items.length; i++) {
|
|
71
|
-
throw new NodeOperationError(this.getNode(), 'error', { itemIndex: i });
|
|
72
|
-
}
|
|
73
|
-
`),
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
name: 'NodeOperationError with itemIndex shorthand in C-style for loop',
|
|
77
|
-
...createNodeWithExecute(`
|
|
78
|
-
const items = this.getInputData();
|
|
79
|
-
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
80
|
-
throw new NodeOperationError(this.getNode(), 'error', { itemIndex });
|
|
81
|
-
}
|
|
82
|
-
`),
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: 'NodeApiError with itemIndex in C-style for loop',
|
|
86
|
-
...createNodeWithExecute(`
|
|
87
|
-
const items = this.getInputData();
|
|
88
|
-
for (let i = 0; i < items.length; i++) {
|
|
89
|
-
throw new NodeApiError(this.getNode(), error, { itemIndex: i });
|
|
90
|
-
}
|
|
91
|
-
`),
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'NodeOperationError with itemIndex in for...of loop',
|
|
95
|
-
...createNodeWithExecute(`
|
|
96
|
-
const items = this.getInputData();
|
|
97
|
-
for (const [i, item] of items.entries()) {
|
|
98
|
-
throw new NodeOperationError(this.getNode(), 'error', { itemIndex: i });
|
|
99
|
-
}
|
|
100
|
-
`),
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: 'NodeOperationError with itemIndex in for...of directly over getInputData()',
|
|
104
|
-
...createNodeWithExecute(`
|
|
105
|
-
let i = 0;
|
|
106
|
-
for (const item of this.getInputData()) {
|
|
107
|
-
throw new NodeOperationError(this.getNode(), 'error', { itemIndex: i++ });
|
|
108
|
-
}
|
|
109
|
-
`),
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
name: 'NodeOperationError with variable as 3rd arg (cannot statically verify — skip)',
|
|
113
|
-
...createNodeWithExecute(`
|
|
114
|
-
const items = this.getInputData();
|
|
115
|
-
for (let i = 0; i < items.length; i++) {
|
|
116
|
-
const opts = { itemIndex: i };
|
|
117
|
-
throw new NodeOperationError(this.getNode(), 'error', opts);
|
|
118
|
-
}
|
|
119
|
-
`),
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
name: 'NodeOperationError with spread plus explicit itemIndex in options',
|
|
123
|
-
...createNodeWithExecute(`
|
|
124
|
-
const items = this.getInputData();
|
|
125
|
-
for (let i = 0; i < items.length; i++) {
|
|
126
|
-
throw new NodeOperationError(this.getNode(), 'error', { ...opts, itemIndex: i });
|
|
127
|
-
}
|
|
128
|
-
`),
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: 'NodeOperationError outside execute() method is not flagged',
|
|
132
|
-
filename: NODE_FILENAME,
|
|
133
|
-
code: `
|
|
134
|
-
import type { INodeType, INodeTypeDescription, IWebhookFunctions, IWebhookResponseData } from 'n8n-workflow';
|
|
135
|
-
import { NodeOperationError } from 'n8n-workflow';
|
|
136
|
-
|
|
137
|
-
export class TestNode implements INodeType {
|
|
138
|
-
description: INodeTypeDescription = {
|
|
139
|
-
displayName: 'Test Node',
|
|
140
|
-
name: 'testNode',
|
|
141
|
-
group: ['trigger'],
|
|
142
|
-
version: 1,
|
|
143
|
-
description: 'A test node',
|
|
144
|
-
defaults: { name: 'Test Node' },
|
|
145
|
-
inputs: [],
|
|
146
|
-
outputs: ['main'],
|
|
147
|
-
webhooks: [{ name: 'default', httpMethod: 'POST', responseMode: 'onReceived', path: 'webhook' }],
|
|
148
|
-
properties: [],
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
|
152
|
-
const items = this.getInputData();
|
|
153
|
-
for (let i = 0; i < items.length; i++) {
|
|
154
|
-
throw new NodeOperationError(this.getNode(), 'webhook error');
|
|
155
|
-
}
|
|
156
|
-
return { workflowData: [[]] };
|
|
157
|
-
}
|
|
158
|
-
}`,
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
name: 'NodeOperationError in nested non-item for loop inside item loop is allowed',
|
|
162
|
-
...createNodeWithExecute(`
|
|
163
|
-
const items = this.getInputData();
|
|
164
|
-
for (let i = 0; i < items.length; i++) {
|
|
165
|
-
const options = ['a', 'b'];
|
|
166
|
-
for (let j = 0; j < options.length; j++) {
|
|
167
|
-
throw new NodeOperationError(this.getNode(), 'error', { itemIndex: i });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
`),
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
invalid: [
|
|
174
|
-
{
|
|
175
|
-
name: 'NodeOperationError without any options in C-style for loop',
|
|
176
|
-
...createNodeWithExecute(`
|
|
177
|
-
const items = this.getInputData();
|
|
178
|
-
for (let i = 0; i < items.length; i++) {
|
|
179
|
-
throw new NodeOperationError(this.getNode(), 'error');
|
|
180
|
-
}
|
|
181
|
-
`),
|
|
182
|
-
errors: [{ messageId: 'missingItemIndex', data: { errorClass: 'NodeOperationError' } }],
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
name: 'NodeOperationError with empty options object in C-style for loop',
|
|
186
|
-
...createNodeWithExecute(`
|
|
187
|
-
const items = this.getInputData();
|
|
188
|
-
for (let i = 0; i < items.length; i++) {
|
|
189
|
-
throw new NodeOperationError(this.getNode(), 'error', {});
|
|
190
|
-
}
|
|
191
|
-
`),
|
|
192
|
-
errors: [{ messageId: 'missingItemIndex', data: { errorClass: 'NodeOperationError' } }],
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
name: 'NodeOperationError with options but missing itemIndex in C-style for loop',
|
|
196
|
-
...createNodeWithExecute(`
|
|
197
|
-
const items = this.getInputData();
|
|
198
|
-
for (let i = 0; i < items.length; i++) {
|
|
199
|
-
throw new NodeOperationError(this.getNode(), 'error', { description: 'something' });
|
|
200
|
-
}
|
|
201
|
-
`),
|
|
202
|
-
errors: [{ messageId: 'missingItemIndex', data: { errorClass: 'NodeOperationError' } }],
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
name: 'NodeApiError without itemIndex in C-style for loop',
|
|
206
|
-
...createNodeWithExecute(`
|
|
207
|
-
const items = this.getInputData();
|
|
208
|
-
for (let i = 0; i < items.length; i++) {
|
|
209
|
-
throw new NodeApiError(this.getNode(), error);
|
|
210
|
-
}
|
|
211
|
-
`),
|
|
212
|
-
errors: [{ messageId: 'missingItemIndex', data: { errorClass: 'NodeApiError' } }],
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
name: 'NodeOperationError without itemIndex in for...of over items variable',
|
|
216
|
-
...createNodeWithExecute(`
|
|
217
|
-
const items = this.getInputData();
|
|
218
|
-
for (const item of items) {
|
|
219
|
-
throw new NodeOperationError(this.getNode(), 'error');
|
|
220
|
-
}
|
|
221
|
-
`),
|
|
222
|
-
errors: [{ messageId: 'missingItemIndex', data: { errorClass: 'NodeOperationError' } }],
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
name: 'NodeOperationError without itemIndex in for...of directly over getInputData()',
|
|
226
|
-
...createNodeWithExecute(`
|
|
227
|
-
for (const item of this.getInputData()) {
|
|
228
|
-
throw new NodeOperationError(this.getNode(), 'error');
|
|
229
|
-
}
|
|
230
|
-
`),
|
|
231
|
-
errors: [{ messageId: 'missingItemIndex', data: { errorClass: 'NodeOperationError' } }],
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
name: 'multiple errors in the same item loop',
|
|
235
|
-
...createNodeWithExecute(`
|
|
236
|
-
const items = this.getInputData();
|
|
237
|
-
for (let i = 0; i < items.length; i++) {
|
|
238
|
-
if (someCondition) {
|
|
239
|
-
throw new NodeOperationError(this.getNode(), 'error A');
|
|
240
|
-
}
|
|
241
|
-
throw new NodeApiError(this.getNode(), error);
|
|
242
|
-
}
|
|
243
|
-
`),
|
|
244
|
-
errors: [
|
|
245
|
-
{ messageId: 'missingItemIndex', data: { errorClass: 'NodeOperationError' } },
|
|
246
|
-
{ messageId: 'missingItemIndex', data: { errorClass: 'NodeApiError' } },
|
|
247
|
-
],
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
name: 'NodeOperationError without itemIndex when loop variable is named itemIndex',
|
|
251
|
-
...createNodeWithExecute(`
|
|
252
|
-
const items = this.getInputData();
|
|
253
|
-
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
254
|
-
throw new NodeOperationError(this.getNode(), 'error', { description: 'oops' });
|
|
255
|
-
}
|
|
256
|
-
`),
|
|
257
|
-
errors: [{ messageId: 'missingItemIndex', data: { errorClass: 'NodeOperationError' } }],
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
name: 'NodeOperationError with spread-only options (spread does not guarantee itemIndex)',
|
|
261
|
-
...createNodeWithExecute(`
|
|
262
|
-
const items = this.getInputData();
|
|
263
|
-
for (let i = 0; i < items.length; i++) {
|
|
264
|
-
throw new NodeOperationError(this.getNode(), 'error', { ...opts });
|
|
265
|
-
}
|
|
266
|
-
`),
|
|
267
|
-
errors: [{ messageId: 'missingItemIndex', data: { errorClass: 'NodeOperationError' } }],
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
name: 'NodeOperationError without itemIndex with non-standard items variable name',
|
|
271
|
-
...createNodeWithExecute(`
|
|
272
|
-
const inputItems = this.getInputData();
|
|
273
|
-
for (let i = 0; i < inputItems.length; i++) {
|
|
274
|
-
throw new NodeOperationError(this.getNode(), 'error');
|
|
275
|
-
}
|
|
276
|
-
`),
|
|
277
|
-
errors: [{ messageId: 'missingItemIndex', data: { errorClass: 'NodeOperationError' } }],
|
|
278
|
-
},
|
|
279
|
-
],
|
|
280
|
-
});
|