@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.
Files changed (106) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/README.md +39 -52
  3. package/dist/plugin.d.ts +0 -78
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +0 -26
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/rules/index.d.ts +0 -13
  8. package/dist/rules/index.d.ts.map +1 -1
  9. package/dist/rules/index.js +0 -26
  10. package/dist/rules/index.js.map +1 -1
  11. package/dist/typecheck.tsbuildinfo +1 -1
  12. package/docs/rules/no-forbidden-lifecycle-scripts.md +1 -1
  13. package/package.json +4 -4
  14. package/src/plugin.ts +0 -26
  15. package/src/rules/index.ts +0 -26
  16. package/dist/rules/cred-class-name-field-conventions.d.ts +0 -2
  17. package/dist/rules/cred-class-name-field-conventions.d.ts.map +0 -1
  18. package/dist/rules/cred-class-name-field-conventions.js +0 -89
  19. package/dist/rules/cred-class-name-field-conventions.js.map +0 -1
  20. package/dist/rules/cred-class-name-suffix.d.ts +0 -2
  21. package/dist/rules/cred-class-name-suffix.d.ts.map +0 -1
  22. package/dist/rules/cred-class-name-suffix.js +0 -53
  23. package/dist/rules/cred-class-name-suffix.js.map +0 -1
  24. package/dist/rules/cred-filename-against-convention.d.ts +0 -2
  25. package/dist/rules/cred-filename-against-convention.d.ts.map +0 -1
  26. package/dist/rules/cred-filename-against-convention.js +0 -42
  27. package/dist/rules/cred-filename-against-convention.js.map +0 -1
  28. package/dist/rules/no-asterisk-in-option-names.d.ts +0 -2
  29. package/dist/rules/no-asterisk-in-option-names.d.ts.map +0 -1
  30. package/dist/rules/no-asterisk-in-option-names.js +0 -90
  31. package/dist/rules/no-asterisk-in-option-names.js.map +0 -1
  32. package/dist/rules/no-duplicate-param-options.d.ts +0 -2
  33. package/dist/rules/no-duplicate-param-options.d.ts.map +0 -1
  34. package/dist/rules/no-duplicate-param-options.js +0 -97
  35. package/dist/rules/no-duplicate-param-options.js.map +0 -1
  36. package/dist/rules/node-class-description-name-camelcase.d.ts +0 -2
  37. package/dist/rules/node-class-description-name-camelcase.d.ts.map +0 -1
  38. package/dist/rules/node-class-description-name-camelcase.js +0 -92
  39. package/dist/rules/node-class-description-name-camelcase.js.map +0 -1
  40. package/dist/rules/node-filename-against-convention.d.ts +0 -2
  41. package/dist/rules/node-filename-against-convention.d.ts.map +0 -1
  42. package/dist/rules/node-filename-against-convention.js +0 -61
  43. package/dist/rules/node-filename-against-convention.js.map +0 -1
  44. package/dist/rules/node-operation-error-itemindex.d.ts +0 -12
  45. package/dist/rules/node-operation-error-itemindex.d.ts.map +0 -1
  46. package/dist/rules/node-operation-error-itemindex.js +0 -184
  47. package/dist/rules/node-operation-error-itemindex.js.map +0 -1
  48. package/dist/rules/options-sorted-alphabetically.d.ts +0 -2
  49. package/dist/rules/options-sorted-alphabetically.d.ts.map +0 -1
  50. package/dist/rules/options-sorted-alphabetically.js +0 -95
  51. package/dist/rules/options-sorted-alphabetically.js.map +0 -1
  52. package/dist/rules/require-community-node-keyword.d.ts +0 -2
  53. package/dist/rules/require-community-node-keyword.d.ts.map +0 -1
  54. package/dist/rules/require-community-node-keyword.js +0 -65
  55. package/dist/rules/require-community-node-keyword.js.map +0 -1
  56. package/dist/rules/require-continue-on-fail.d.ts +0 -2
  57. package/dist/rules/require-continue-on-fail.d.ts.map +0 -1
  58. package/dist/rules/require-continue-on-fail.js +0 -76
  59. package/dist/rules/require-continue-on-fail.js.map +0 -1
  60. package/dist/rules/require-mit-license.d.ts +0 -2
  61. package/dist/rules/require-mit-license.d.ts.map +0 -1
  62. package/dist/rules/require-mit-license.js +0 -57
  63. package/dist/rules/require-mit-license.js.map +0 -1
  64. package/dist/rules/require-param-default.d.ts +0 -15
  65. package/dist/rules/require-param-default.d.ts.map +0 -1
  66. package/dist/rules/require-param-default.js +0 -75
  67. package/dist/rules/require-param-default.js.map +0 -1
  68. package/docs/rules/cred-class-name-field-conventions.md +0 -38
  69. package/docs/rules/cred-class-name-suffix.md +0 -46
  70. package/docs/rules/cred-filename-against-convention.md +0 -42
  71. package/docs/rules/no-asterisk-in-option-names.md +0 -61
  72. package/docs/rules/no-duplicate-param-options.md +0 -46
  73. package/docs/rules/node-class-description-name-camelcase.md +0 -41
  74. package/docs/rules/node-filename-against-convention.md +0 -50
  75. package/docs/rules/node-operation-error-itemindex.md +0 -81
  76. package/docs/rules/options-sorted-alphabetically.md +0 -63
  77. package/docs/rules/require-community-node-keyword.md +0 -45
  78. package/docs/rules/require-continue-on-fail.md +0 -56
  79. package/docs/rules/require-mit-license.md +0 -38
  80. package/docs/rules/require-param-default.md +0 -60
  81. package/src/rules/cred-class-name-field-conventions.test.ts +0 -121
  82. package/src/rules/cred-class-name-field-conventions.ts +0 -102
  83. package/src/rules/cred-class-name-suffix.test.ts +0 -74
  84. package/src/rules/cred-class-name-suffix.ts +0 -57
  85. package/src/rules/cred-filename-against-convention.test.ts +0 -72
  86. package/src/rules/cred-filename-against-convention.ts +0 -48
  87. package/src/rules/no-asterisk-in-option-names.test.ts +0 -185
  88. package/src/rules/no-asterisk-in-option-names.ts +0 -111
  89. package/src/rules/no-duplicate-param-options.test.ts +0 -125
  90. package/src/rules/no-duplicate-param-options.ts +0 -125
  91. package/src/rules/node-class-description-name-camelcase.test.ts +0 -121
  92. package/src/rules/node-class-description-name-camelcase.ts +0 -114
  93. package/src/rules/node-filename-against-convention.test.ts +0 -115
  94. package/src/rules/node-filename-against-convention.ts +0 -76
  95. package/src/rules/node-operation-error-itemindex.test.ts +0 -280
  96. package/src/rules/node-operation-error-itemindex.ts +0 -223
  97. package/src/rules/options-sorted-alphabetically.test.ts +0 -468
  98. package/src/rules/options-sorted-alphabetically.ts +0 -129
  99. package/src/rules/require-community-node-keyword.test.ts +0 -82
  100. package/src/rules/require-community-node-keyword.ts +0 -78
  101. package/src/rules/require-continue-on-fail.test.ts +0 -129
  102. package/src/rules/require-continue-on-fail.ts +0 -88
  103. package/src/rules/require-mit-license.test.ts +0 -67
  104. package/src/rules/require-mit-license.ts +0 -65
  105. package/src/rules/require-param-default.test.ts +0 -227
  106. package/src/rules/require-param-default.ts +0 -89
@@ -1,223 +0,0 @@
1
- /**
2
- * Flags `new NodeOperationError(...)` or `new NodeApiError(...)` inside item
3
- * loops in `execute()` methods that omit `{ itemIndex }` from the options
4
- * argument. Without it, n8n cannot associate the error with the specific item
5
- * that caused it, breaking per-item error reporting and `continueOnFail`.
6
- *
7
- * "Item loop" means a `for` or `for...of` that iterates over the result of
8
- * `this.getInputData()` (or a variable initialised from it). Errors outside
9
- * such loops — e.g. in webhook handlers or trigger setup — are not flagged.
10
- */
11
-
12
- import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
13
-
14
- import { createRule, findObjectProperty, isFileType, isNodeTypeClass } from '../utils/index.js';
15
-
16
- const ITEM_ERROR_CLASSES = new Set(['NodeOperationError', 'NodeApiError']);
17
-
18
- /** Returns true when `node` is a bare `this.getInputData(...)` call. */
19
- function isGetInputDataCall(node: TSESTree.CallExpression): boolean {
20
- return (
21
- node.callee.type === AST_NODE_TYPES.MemberExpression &&
22
- node.callee.object.type === AST_NODE_TYPES.ThisExpression &&
23
- node.callee.property.type === AST_NODE_TYPES.Identifier &&
24
- node.callee.property.name === 'getInputData'
25
- );
26
- }
27
-
28
- /** Returns true when `node` is `<varName>.length` for any name in `varNames`. */
29
- function isLengthAccessOnVariable(node: TSESTree.Node, varNames: Set<string>): boolean {
30
- return (
31
- node.type === AST_NODE_TYPES.MemberExpression &&
32
- !node.computed &&
33
- node.property.type === AST_NODE_TYPES.Identifier &&
34
- node.property.name === 'length' &&
35
- node.object.type === AST_NODE_TYPES.Identifier &&
36
- varNames.has(node.object.name)
37
- );
38
- }
39
-
40
- /**
41
- * Returns true when the `for` test condition references `<itemVar>.length`,
42
- * indicating that the loop iterates over an items array.
43
- */
44
- function isItemForLoop(node: TSESTree.ForStatement, itemVarNames: Set<string>): boolean {
45
- if (!node.test || node.test.type !== AST_NODE_TYPES.BinaryExpression) return false;
46
-
47
- const { left, right } = node.test;
48
- return (
49
- isLengthAccessOnVariable(left, itemVarNames) || isLengthAccessOnVariable(right, itemVarNames)
50
- );
51
- }
52
-
53
- /**
54
- * Returns true when the `for...of` iterable is an items variable or a direct
55
- * `this.getInputData()` call.
56
- */
57
- function isItemForOfLoop(node: TSESTree.ForOfStatement, itemVarNames: Set<string>): boolean {
58
- const { right } = node;
59
-
60
- if (right.type === AST_NODE_TYPES.Identifier && itemVarNames.has(right.name)) {
61
- return true;
62
- }
63
-
64
- return right.type === AST_NODE_TYPES.CallExpression && isGetInputDataCall(right);
65
- }
66
-
67
- /**
68
- * Returns true when the `NodeOperationError` / `NodeApiError` constructor call
69
- * already has an `{ itemIndex }` property in its options argument, or when the
70
- * options argument cannot be statically inspected (variable / spread) — in
71
- * which case we give the benefit of the doubt.
72
- */
73
- function hasItemIndexOption(node: TSESTree.NewExpression): boolean {
74
- const { arguments: args } = node;
75
-
76
- if (args.length < 3) return false;
77
-
78
- const optionsArg = args[2];
79
-
80
- // Non-object-literal (bare variable reference) — can't statically check, assume OK.
81
- if (!optionsArg || optionsArg.type !== AST_NODE_TYPES.ObjectExpression) {
82
- return true;
83
- }
84
-
85
- // itemIndex must be an explicit own property of the options object.
86
- // Spread elements (e.g. { ...opts }) are not sufficient — they may not
87
- // include itemIndex and would silently bypass this requirement.
88
- return findObjectProperty(optionsArg, 'itemIndex') !== null;
89
- }
90
-
91
- export const NodeOperationErrorItemIndexRule = createRule({
92
- name: 'node-operation-error-itemindex',
93
- meta: {
94
- type: 'problem',
95
- docs: {
96
- description:
97
- 'Require { itemIndex } in NodeOperationError / NodeApiError options inside item loops',
98
- },
99
- messages: {
100
- missingItemIndex:
101
- '`new {{ errorClass }}(...)` inside an item loop must include `{ itemIndex }` as the ' +
102
- 'third argument so n8n can associate the error with the failing item.',
103
- },
104
- schema: [],
105
- },
106
- defaultOptions: [],
107
- create(context) {
108
- if (!isFileType(context.filename, '.node.ts')) {
109
- return {};
110
- }
111
-
112
- let inNodeTypeClass = false;
113
- let inExecuteMethod = false;
114
-
115
- /** Names of variables initialised from `this.getInputData()` in the current execute() scope. */
116
- const itemVariableNames = new Set<string>();
117
-
118
- /** AST nodes for loops that are confirmed item loops. */
119
- const itemLoopNodes = new Set<TSESTree.ForStatement | TSESTree.ForOfStatement>();
120
-
121
- /** Number of currently open item loops (supports nested loops). */
122
- let itemLoopDepth = 0;
123
-
124
- function resetExecuteState() {
125
- inExecuteMethod = false;
126
- itemVariableNames.clear();
127
- itemLoopNodes.clear();
128
- itemLoopDepth = 0;
129
- }
130
-
131
- return {
132
- ClassDeclaration(node) {
133
- if (isNodeTypeClass(node)) {
134
- inNodeTypeClass = true;
135
- }
136
- },
137
-
138
- 'ClassDeclaration:exit'() {
139
- inNodeTypeClass = false;
140
- resetExecuteState();
141
- },
142
-
143
- MethodDefinition(node: TSESTree.MethodDefinition) {
144
- if (
145
- inNodeTypeClass &&
146
- node.key.type === AST_NODE_TYPES.Identifier &&
147
- node.key.name === 'execute'
148
- ) {
149
- inExecuteMethod = true;
150
- }
151
- },
152
-
153
- 'MethodDefinition:exit'(node: TSESTree.MethodDefinition) {
154
- if (
155
- inExecuteMethod &&
156
- node.key.type === AST_NODE_TYPES.Identifier &&
157
- node.key.name === 'execute'
158
- ) {
159
- resetExecuteState();
160
- }
161
- },
162
-
163
- VariableDeclarator(node: TSESTree.VariableDeclarator) {
164
- if (!inExecuteMethod) return;
165
- if (!node.init) return;
166
- if (node.id.type !== AST_NODE_TYPES.Identifier) return;
167
-
168
- if (node.init.type === AST_NODE_TYPES.CallExpression && isGetInputDataCall(node.init)) {
169
- itemVariableNames.add(node.id.name);
170
- }
171
- },
172
-
173
- ForStatement(node: TSESTree.ForStatement) {
174
- if (!inExecuteMethod) return;
175
- if (isItemForLoop(node, itemVariableNames)) {
176
- itemLoopNodes.add(node);
177
- itemLoopDepth++;
178
- }
179
- },
180
-
181
- 'ForStatement:exit'(node: TSESTree.ForStatement) {
182
- if (itemLoopNodes.has(node)) {
183
- itemLoopNodes.delete(node);
184
- itemLoopDepth--;
185
- }
186
- },
187
-
188
- ForOfStatement(node: TSESTree.ForOfStatement) {
189
- if (!inExecuteMethod) return;
190
- if (isItemForOfLoop(node, itemVariableNames)) {
191
- itemLoopNodes.add(node);
192
- itemLoopDepth++;
193
- }
194
- },
195
-
196
- 'ForOfStatement:exit'(node: TSESTree.ForOfStatement) {
197
- if (itemLoopNodes.has(node)) {
198
- itemLoopNodes.delete(node);
199
- itemLoopDepth--;
200
- }
201
- },
202
-
203
- NewExpression(node: TSESTree.NewExpression) {
204
- if (itemLoopDepth === 0) return;
205
-
206
- if (
207
- node.callee.type !== AST_NODE_TYPES.Identifier ||
208
- !ITEM_ERROR_CLASSES.has(node.callee.name)
209
- ) {
210
- return;
211
- }
212
-
213
- if (!hasItemIndexOption(node)) {
214
- context.report({
215
- node,
216
- messageId: 'missingItemIndex',
217
- data: { errorClass: node.callee.name },
218
- });
219
- }
220
- },
221
- };
222
- },
223
- });
@@ -1,468 +0,0 @@
1
- import { RuleTester } from '@typescript-eslint/rule-tester';
2
-
3
- import { OptionsSortedAlphabeticallyRule } from './options-sorted-alphabetically.js';
4
-
5
- const ruleTester = new RuleTester();
6
-
7
- ruleTester.run('options-sorted-alphabetically', OptionsSortedAlphabeticallyRule, {
8
- valid: [
9
- {
10
- name: 'resource options already sorted alphabetically',
11
- filename: '/tmp/TestNode.node.ts',
12
- code: `
13
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
14
-
15
- export class TestNode implements INodeType {
16
- description: INodeTypeDescription = {
17
- displayName: 'Test Node',
18
- name: 'testNode',
19
- properties: [
20
- {
21
- displayName: 'Resource',
22
- name: 'resource',
23
- type: 'options',
24
- options: [
25
- { name: 'Contact', value: 'contact' },
26
- { name: 'Project', value: 'project' },
27
- { name: 'User', value: 'user' },
28
- ],
29
- default: 'contact',
30
- },
31
- ],
32
- };
33
- }
34
- `,
35
- },
36
- {
37
- name: 'operation options already sorted alphabetically',
38
- filename: '/tmp/TestNode.node.ts',
39
- code: `
40
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
41
-
42
- export class TestNode implements INodeType {
43
- description: INodeTypeDescription = {
44
- displayName: 'Test Node',
45
- name: 'testNode',
46
- properties: [
47
- {
48
- displayName: 'Operation',
49
- name: 'operation',
50
- type: 'options',
51
- options: [
52
- { name: 'Create', value: 'create' },
53
- { name: 'Delete', value: 'delete' },
54
- { name: 'Get', value: 'get' },
55
- { name: 'Update', value: 'update' },
56
- ],
57
- default: 'get',
58
- },
59
- ],
60
- };
61
- }
62
- `,
63
- },
64
- {
65
- name: 'non-ASCII names sorted correctly via localeCompare',
66
- filename: '/tmp/TestNode.node.ts',
67
- code: `
68
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
69
-
70
- export class TestNode implements INodeType {
71
- description: INodeTypeDescription = {
72
- displayName: 'Test Node',
73
- name: 'testNode',
74
- properties: [
75
- {
76
- displayName: 'Resource',
77
- name: 'resource',
78
- type: 'options',
79
- options: [
80
- { name: 'Árbol', value: 'arbol' },
81
- { name: 'Empresa', value: 'empresa' },
82
- { name: 'Usuario', value: 'usuario' },
83
- ],
84
- default: 'arbol',
85
- },
86
- ],
87
- };
88
- }
89
- `,
90
- },
91
- {
92
- name: 'single option is trivially sorted',
93
- filename: '/tmp/TestNode.node.ts',
94
- code: `
95
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
96
-
97
- export class TestNode implements INodeType {
98
- description: INodeTypeDescription = {
99
- displayName: 'Test Node',
100
- name: 'testNode',
101
- properties: [
102
- {
103
- displayName: 'Resource',
104
- name: 'resource',
105
- type: 'options',
106
- options: [
107
- { name: 'User', value: 'user' },
108
- ],
109
- default: 'user',
110
- },
111
- ],
112
- };
113
- }
114
- `,
115
- },
116
- {
117
- name: 'non-options type parameter is ignored',
118
- filename: '/tmp/TestNode.node.ts',
119
- code: `
120
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
121
-
122
- export class TestNode implements INodeType {
123
- description: INodeTypeDescription = {
124
- displayName: 'Test Node',
125
- name: 'testNode',
126
- properties: [
127
- {
128
- displayName: 'Name',
129
- name: 'name',
130
- type: 'string',
131
- default: '',
132
- },
133
- ],
134
- };
135
- }
136
- `,
137
- },
138
- {
139
- name: 'non-node class is ignored',
140
- filename: '/tmp/TestNode.node.ts',
141
- code: `
142
- export class NotANode {
143
- description = {
144
- properties: [
145
- {
146
- displayName: 'Resource',
147
- name: 'resource',
148
- type: 'options',
149
- options: [
150
- { name: 'User', value: 'user' },
151
- { name: 'Contact', value: 'contact' },
152
- ],
153
- default: 'user',
154
- },
155
- ],
156
- };
157
- }
158
- `,
159
- },
160
- {
161
- name: 'non-.node.ts file is ignored',
162
- filename: '/tmp/TestHelper.ts',
163
- code: `
164
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
165
-
166
- export class TestNode implements INodeType {
167
- description: INodeTypeDescription = {
168
- displayName: 'Test Node',
169
- name: 'testNode',
170
- properties: [
171
- {
172
- displayName: 'Resource',
173
- name: 'resource',
174
- type: 'options',
175
- options: [
176
- { name: 'User', value: 'user' },
177
- { name: 'Contact', value: 'contact' },
178
- ],
179
- default: 'user',
180
- },
181
- ],
182
- };
183
- }
184
- `,
185
- },
186
- {
187
- name: 'description assigned from a variable is skipped',
188
- filename: '/tmp/TestNode.node.ts',
189
- code: `
190
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
191
-
192
- const desc: INodeTypeDescription = {} as INodeTypeDescription;
193
-
194
- export class TestNode implements INodeType {
195
- description = desc;
196
- }
197
- `,
198
- },
199
- {
200
- name: 'node with no properties array is skipped',
201
- filename: '/tmp/TestNode.node.ts',
202
- code: `
203
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
204
-
205
- export class TestNode implements INodeType {
206
- description: INodeTypeDescription = {
207
- displayName: 'Test Node',
208
- name: 'testNode',
209
- };
210
- }
211
- `,
212
- },
213
- {
214
- name: 'spread element in properties array is skipped gracefully',
215
- filename: '/tmp/TestNode.node.ts',
216
- code: `
217
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
218
-
219
- const extraProps = [{ displayName: 'Extra', name: 'extra', type: 'string', default: '' }];
220
-
221
- export class TestNode implements INodeType {
222
- description: INodeTypeDescription = {
223
- displayName: 'Test Node',
224
- name: 'testNode',
225
- properties: [
226
- ...extraProps,
227
- ],
228
- };
229
- }
230
- `,
231
- },
232
- {
233
- name: 'options with a spread element are skipped (dynamic options)',
234
- filename: '/tmp/TestNode.node.ts',
235
- code: `
236
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
237
-
238
- const extraOption = { name: 'Extra', value: 'extra' };
239
-
240
- export class TestNode implements INodeType {
241
- description: INodeTypeDescription = {
242
- displayName: 'Test Node',
243
- name: 'testNode',
244
- properties: [
245
- {
246
- displayName: 'Resource',
247
- name: 'resource',
248
- type: 'options',
249
- options: [
250
- { name: 'User', value: 'user' },
251
- ...([extraOption]),
252
- ],
253
- default: 'user',
254
- },
255
- ],
256
- };
257
- }
258
- `,
259
- },
260
- {
261
- name: 'options with a dynamic name value are skipped',
262
- filename: '/tmp/TestNode.node.ts',
263
- code: `
264
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
265
-
266
- const dynamicName = 'Dynamic';
267
-
268
- export class TestNode implements INodeType {
269
- description: INodeTypeDescription = {
270
- displayName: 'Test Node',
271
- name: 'testNode',
272
- properties: [
273
- {
274
- displayName: 'Resource',
275
- name: 'resource',
276
- type: 'options',
277
- options: [
278
- { name: dynamicName, value: 'dynamic' },
279
- { name: 'User', value: 'user' },
280
- ],
281
- default: 'user',
282
- },
283
- ],
284
- };
285
- }
286
- `,
287
- },
288
- ],
289
- invalid: [
290
- {
291
- name: 'resource options not sorted alphabetically',
292
- filename: '/tmp/TestNode.node.ts',
293
- code: `
294
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
295
-
296
- export class TestNode implements INodeType {
297
- description: INodeTypeDescription = {
298
- displayName: 'Test Node',
299
- name: 'testNode',
300
- properties: [
301
- {
302
- displayName: 'Resource',
303
- name: 'resource',
304
- type: 'options',
305
- options: [
306
- { name: 'User', value: 'user' },
307
- { name: 'Contact', value: 'contact' },
308
- { name: 'Project', value: 'project' },
309
- ],
310
- default: 'user',
311
- },
312
- ],
313
- };
314
- }
315
- `,
316
- errors: [
317
- {
318
- messageId: 'optionsNotSorted',
319
- data: { displayName: 'Resource', expectedOrder: 'Contact, Project, User' },
320
- },
321
- ],
322
- },
323
- {
324
- name: 'operation options not sorted alphabetically',
325
- filename: '/tmp/TestNode.node.ts',
326
- code: `
327
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
328
-
329
- export class TestNode implements INodeType {
330
- description: INodeTypeDescription = {
331
- displayName: 'Test Node',
332
- name: 'testNode',
333
- properties: [
334
- {
335
- displayName: 'Operation',
336
- name: 'operation',
337
- type: 'options',
338
- options: [
339
- { name: 'Update', value: 'update' },
340
- { name: 'Create', value: 'create' },
341
- { name: 'Delete', value: 'delete' },
342
- { name: 'Get', value: 'get' },
343
- ],
344
- default: 'get',
345
- },
346
- ],
347
- };
348
- }
349
- `,
350
- errors: [
351
- {
352
- messageId: 'optionsNotSorted',
353
- data: { displayName: 'Operation', expectedOrder: 'Create, Delete, Get, Update' },
354
- },
355
- ],
356
- },
357
- {
358
- name: 'other options-type parameter not sorted alphabetically',
359
- filename: '/tmp/TestNode.node.ts',
360
- code: `
361
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
362
-
363
- export class TestNode implements INodeType {
364
- description: INodeTypeDescription = {
365
- displayName: 'Test Node',
366
- name: 'testNode',
367
- properties: [
368
- {
369
- displayName: 'Model',
370
- name: 'model',
371
- type: 'options',
372
- options: [
373
- { name: 'GPT-4', value: 'gpt-4' },
374
- { name: 'Claude', value: 'claude' },
375
- { name: 'Gemini', value: 'gemini' },
376
- ],
377
- default: 'gpt-4',
378
- },
379
- ],
380
- };
381
- }
382
- `,
383
- errors: [
384
- {
385
- messageId: 'optionsNotSorted',
386
- data: { displayName: 'Model', expectedOrder: 'Claude, Gemini, GPT-4' },
387
- },
388
- ],
389
- },
390
- {
391
- name: 'options-type parameter without displayName falls back to "unknown"',
392
- filename: '/tmp/TestNode.node.ts',
393
- code: `
394
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
395
-
396
- export class TestNode implements INodeType {
397
- description: INodeTypeDescription = {
398
- displayName: 'Test Node',
399
- name: 'testNode',
400
- properties: [
401
- {
402
- name: 'resource',
403
- type: 'options',
404
- options: [
405
- { name: 'User', value: 'user' },
406
- { name: 'Contact', value: 'contact' },
407
- ],
408
- default: 'user',
409
- },
410
- ],
411
- };
412
- }
413
- `,
414
- errors: [
415
- {
416
- messageId: 'optionsNotSorted',
417
- data: { displayName: 'unknown', expectedOrder: 'Contact, User' },
418
- },
419
- ],
420
- },
421
- {
422
- name: 'multiple unsorted parameters each report an error',
423
- filename: '/tmp/TestNode.node.ts',
424
- code: `
425
- import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
426
-
427
- export class TestNode implements INodeType {
428
- description: INodeTypeDescription = {
429
- displayName: 'Test Node',
430
- name: 'testNode',
431
- properties: [
432
- {
433
- displayName: 'Resource',
434
- name: 'resource',
435
- type: 'options',
436
- options: [
437
- { name: 'User', value: 'user' },
438
- { name: 'Contact', value: 'contact' },
439
- ],
440
- default: 'user',
441
- },
442
- {
443
- displayName: 'Operation',
444
- name: 'operation',
445
- type: 'options',
446
- options: [
447
- { name: 'Update', value: 'update' },
448
- { name: 'Create', value: 'create' },
449
- ],
450
- default: 'create',
451
- },
452
- ],
453
- };
454
- }
455
- `,
456
- errors: [
457
- {
458
- messageId: 'optionsNotSorted',
459
- data: { displayName: 'Resource', expectedOrder: 'Contact, User' },
460
- },
461
- {
462
- messageId: 'optionsNotSorted',
463
- data: { displayName: 'Operation', expectedOrder: 'Create, Update' },
464
- },
465
- ],
466
- },
467
- ],
468
- });