@n8n/eslint-plugin-community-nodes 0.23.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 +4 -0
- 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/src/rules/no-forbidden-lifecycle-scripts.test.ts +2 -2
- package/.turbo/turbo-build$colon$unchecked.log +0 -4
- 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,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
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import type { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
-
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
isNodeTypeClass,
|
|
6
|
-
findClassProperty,
|
|
7
|
-
findObjectProperty,
|
|
8
|
-
getStringLiteralValue,
|
|
9
|
-
isFileType,
|
|
10
|
-
createRule,
|
|
11
|
-
} from '../utils/index.js';
|
|
12
|
-
|
|
13
|
-
export const OptionsSortedAlphabeticallyRule = createRule({
|
|
14
|
-
name: 'options-sorted-alphabetically',
|
|
15
|
-
meta: {
|
|
16
|
-
type: 'suggestion',
|
|
17
|
-
docs: {
|
|
18
|
-
description: 'Enforce alphabetical ordering of options arrays in n8n node properties',
|
|
19
|
-
},
|
|
20
|
-
messages: {
|
|
21
|
-
optionsNotSorted:
|
|
22
|
-
'Options in "{{ displayName }}" are not sorted alphabetically. Expected order: {{ expectedOrder }}.',
|
|
23
|
-
},
|
|
24
|
-
schema: [],
|
|
25
|
-
},
|
|
26
|
-
defaultOptions: [],
|
|
27
|
-
create(context) {
|
|
28
|
-
if (!isFileType(context.filename, '.node.ts')) {
|
|
29
|
-
return {};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const analyzeNodeDescription = (descriptionValue: TSESTree.Expression | null): void => {
|
|
33
|
-
if (!descriptionValue || descriptionValue.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const propertiesProperty = findObjectProperty(descriptionValue, 'properties');
|
|
38
|
-
if (
|
|
39
|
-
!propertiesProperty?.value ||
|
|
40
|
-
propertiesProperty.value.type !== AST_NODE_TYPES.ArrayExpression
|
|
41
|
-
) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const property of propertiesProperty.value.elements) {
|
|
46
|
-
if (!property || property.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const typeProperty = findObjectProperty(property, 'type');
|
|
51
|
-
const type = typeProperty ? getStringLiteralValue(typeProperty.value) : null;
|
|
52
|
-
|
|
53
|
-
if (type !== 'options') {
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const optionsProperty = findObjectProperty(property, 'options');
|
|
58
|
-
if (
|
|
59
|
-
!optionsProperty?.value ||
|
|
60
|
-
optionsProperty.value.type !== AST_NODE_TYPES.ArrayExpression
|
|
61
|
-
) {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const optionsArray = optionsProperty.value;
|
|
66
|
-
|
|
67
|
-
// Extract all option names; skip if any name is non-literal (dynamic options)
|
|
68
|
-
const names: string[] = [];
|
|
69
|
-
let hasDynamicName = false;
|
|
70
|
-
|
|
71
|
-
for (const option of optionsArray.elements) {
|
|
72
|
-
if (!option || option.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
73
|
-
hasDynamicName = true;
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
const nameProperty = findObjectProperty(option, 'name');
|
|
77
|
-
const name = nameProperty ? getStringLiteralValue(nameProperty.value) : null;
|
|
78
|
-
if (name === null) {
|
|
79
|
-
hasDynamicName = true;
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
names.push(name);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (hasDynamicName || names.length <= 1) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const sorted = [...names].sort((a, b) =>
|
|
90
|
-
a.localeCompare(b, undefined, { sensitivity: 'base' }),
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const isSorted = names.every((name, i) => name === sorted[i]);
|
|
94
|
-
if (isSorted) {
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const displayNameProperty = findObjectProperty(property, 'displayName');
|
|
99
|
-
const displayName =
|
|
100
|
-
(displayNameProperty ? getStringLiteralValue(displayNameProperty.value) : null) ??
|
|
101
|
-
'unknown';
|
|
102
|
-
|
|
103
|
-
context.report({
|
|
104
|
-
node: optionsArray,
|
|
105
|
-
messageId: 'optionsNotSorted',
|
|
106
|
-
data: {
|
|
107
|
-
displayName,
|
|
108
|
-
expectedOrder: sorted.join(', '),
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
ClassDeclaration(node) {
|
|
116
|
-
if (!isNodeTypeClass(node)) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const descriptionProperty = findClassProperty(node, 'description');
|
|
121
|
-
if (!descriptionProperty) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
analyzeNodeDescription(descriptionProperty.value);
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
},
|
|
129
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
-
|
|
3
|
-
import { RequireCommunityNodeKeywordRule } from './require-community-node-keyword.js';
|
|
4
|
-
|
|
5
|
-
const ruleTester = new RuleTester();
|
|
6
|
-
|
|
7
|
-
ruleTester.run('require-community-node-keyword', RequireCommunityNodeKeywordRule, {
|
|
8
|
-
valid: [
|
|
9
|
-
{
|
|
10
|
-
name: 'keywords array contains required keyword',
|
|
11
|
-
filename: 'package.json',
|
|
12
|
-
code: '{ "name": "n8n-nodes-example", "keywords": ["n8n-community-node-package"] }',
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
name: 'keywords array contains required keyword among others',
|
|
16
|
-
filename: 'package.json',
|
|
17
|
-
code: '{ "name": "n8n-nodes-example", "keywords": ["n8n", "automation", "n8n-community-node-package", "workflow"] }',
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: 'non-package.json file is ignored',
|
|
21
|
-
filename: 'some-config.json',
|
|
22
|
-
code: '{ "name": "n8n-nodes-example" }',
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
name: 'nested objects are not checked',
|
|
26
|
-
filename: 'package.json',
|
|
27
|
-
code: '{ "name": "n8n-nodes-example", "keywords": ["n8n-community-node-package"], "config": { "nested": "value" } }',
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
name: 'objects inside arrays (e.g. contributors) are not flagged',
|
|
31
|
-
filename: 'package.json',
|
|
32
|
-
code: `{
|
|
33
|
-
"name": "n8n-nodes-example",
|
|
34
|
-
"keywords": ["n8n-community-node-package"],
|
|
35
|
-
"contributors": [
|
|
36
|
-
{ "name": "Alice", "email": "alice@example.com" },
|
|
37
|
-
{ "name": "Bob", "email": "bob@example.com" }
|
|
38
|
-
]
|
|
39
|
-
}`,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
invalid: [
|
|
43
|
-
{
|
|
44
|
-
name: 'missing keywords array entirely',
|
|
45
|
-
filename: 'package.json',
|
|
46
|
-
code: '{ "name": "n8n-nodes-example", "version": "1.0.0" }',
|
|
47
|
-
output:
|
|
48
|
-
'{ "name": "n8n-nodes-example", "version": "1.0.0", "keywords": ["n8n-community-node-package"] }',
|
|
49
|
-
errors: [{ messageId: 'missingKeywordsArray' }],
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: 'empty keywords array',
|
|
53
|
-
filename: 'package.json',
|
|
54
|
-
code: '{ "name": "n8n-nodes-example", "keywords": [] }',
|
|
55
|
-
output: '{ "name": "n8n-nodes-example", "keywords": ["n8n-community-node-package"] }',
|
|
56
|
-
errors: [{ messageId: 'missingKeyword' }],
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'keywords array without the required keyword',
|
|
60
|
-
filename: 'package.json',
|
|
61
|
-
code: '{ "name": "n8n-nodes-example", "keywords": ["n8n", "automation"] }',
|
|
62
|
-
output:
|
|
63
|
-
'{ "name": "n8n-nodes-example", "keywords": ["n8n", "automation", "n8n-community-node-package"] }',
|
|
64
|
-
errors: [{ messageId: 'missingKeyword' }],
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: 'keywords array with similar but incorrect keyword',
|
|
68
|
-
filename: 'package.json',
|
|
69
|
-
code: '{ "name": "n8n-nodes-example", "keywords": ["n8n-community-node"] }',
|
|
70
|
-
output:
|
|
71
|
-
'{ "name": "n8n-nodes-example", "keywords": ["n8n-community-node", "n8n-community-node-package"] }',
|
|
72
|
-
errors: [{ messageId: 'missingKeyword' }],
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: 'empty package.json object',
|
|
76
|
-
filename: 'package.json',
|
|
77
|
-
code: '{}',
|
|
78
|
-
output: '{ "keywords": ["n8n-community-node-package"] }',
|
|
79
|
-
errors: [{ messageId: 'missingKeywordsArray' }],
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
});
|