@n8n/eslint-plugin-community-nodes 0.14.0 → 0.16.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 (83) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/README.md +7 -0
  3. package/dist/plugin.d.ts +48 -0
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +16 -0
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/rules/cred-class-name-suffix.d.ts +2 -0
  8. package/dist/rules/cred-class-name-suffix.d.ts.map +1 -0
  9. package/dist/rules/cred-class-name-suffix.js +53 -0
  10. package/dist/rules/cred-class-name-suffix.js.map +1 -0
  11. package/dist/rules/cred-class-oauth2-naming.d.ts +2 -0
  12. package/dist/rules/cred-class-oauth2-naming.d.ts.map +1 -0
  13. package/dist/rules/cred-class-oauth2-naming.js +96 -0
  14. package/dist/rules/cred-class-oauth2-naming.js.map +1 -0
  15. package/dist/rules/index.d.ts +11 -1
  16. package/dist/rules/index.d.ts.map +1 -1
  17. package/dist/rules/index.js +16 -0
  18. package/dist/rules/index.js.map +1 -1
  19. package/dist/rules/n8n-object-validation.d.ts +5 -0
  20. package/dist/rules/n8n-object-validation.d.ts.map +1 -0
  21. package/dist/rules/n8n-object-validation.js +148 -0
  22. package/dist/rules/n8n-object-validation.js.map +1 -0
  23. package/dist/rules/no-builder-hint-leakage.d.ts +7 -0
  24. package/dist/rules/no-builder-hint-leakage.d.ts.map +1 -0
  25. package/dist/rules/no-builder-hint-leakage.js +99 -0
  26. package/dist/rules/no-builder-hint-leakage.js.map +1 -0
  27. package/dist/rules/no-overrides-field.js +1 -1
  28. package/dist/rules/no-overrides-field.js.map +1 -1
  29. package/dist/rules/no-runtime-dependencies.d.ts +2 -0
  30. package/dist/rules/no-runtime-dependencies.d.ts.map +1 -0
  31. package/dist/rules/no-runtime-dependencies.js +41 -0
  32. package/dist/rules/no-runtime-dependencies.js.map +1 -0
  33. package/dist/rules/no-template-placeholders.d.ts +2 -0
  34. package/dist/rules/no-template-placeholders.d.ts.map +1 -0
  35. package/dist/rules/no-template-placeholders.js +57 -0
  36. package/dist/rules/no-template-placeholders.js.map +1 -0
  37. package/dist/rules/node-operation-error-itemindex.d.ts +12 -0
  38. package/dist/rules/node-operation-error-itemindex.d.ts.map +1 -0
  39. package/dist/rules/node-operation-error-itemindex.js +184 -0
  40. package/dist/rules/node-operation-error-itemindex.js.map +1 -0
  41. package/dist/rules/valid-credential-references.d.ts +2 -0
  42. package/dist/rules/valid-credential-references.d.ts.map +1 -0
  43. package/dist/rules/valid-credential-references.js +77 -0
  44. package/dist/rules/valid-credential-references.js.map +1 -0
  45. package/dist/rules/webhook-lifecycle-complete.d.ts +1 -1
  46. package/dist/rules/webhook-lifecycle-complete.d.ts.map +1 -1
  47. package/dist/rules/webhook-lifecycle-complete.js +8 -0
  48. package/dist/rules/webhook-lifecycle-complete.js.map +1 -1
  49. package/dist/utils/ast-utils.d.ts.map +1 -1
  50. package/dist/utils/ast-utils.js +5 -1
  51. package/dist/utils/ast-utils.js.map +1 -1
  52. package/docs/rules/cred-class-name-suffix.md +46 -0
  53. package/docs/rules/cred-class-oauth2-naming.md +68 -0
  54. package/docs/rules/n8n-object-validation.md +93 -0
  55. package/docs/rules/no-overrides-field.md +5 -5
  56. package/docs/rules/no-runtime-dependencies.md +58 -0
  57. package/docs/rules/no-template-placeholders.md +51 -0
  58. package/docs/rules/node-operation-error-itemindex.md +81 -0
  59. package/docs/rules/valid-credential-references.md +78 -0
  60. package/package.json +3 -3
  61. package/src/plugin.ts +16 -0
  62. package/src/rules/cred-class-name-suffix.test.ts +74 -0
  63. package/src/rules/cred-class-name-suffix.ts +57 -0
  64. package/src/rules/cred-class-oauth2-naming.test.ts +197 -0
  65. package/src/rules/cred-class-oauth2-naming.ts +118 -0
  66. package/src/rules/index.ts +16 -0
  67. package/src/rules/n8n-object-validation.test.ts +202 -0
  68. package/src/rules/n8n-object-validation.ts +200 -0
  69. package/src/rules/no-builder-hint-leakage.test.ts +84 -0
  70. package/src/rules/no-builder-hint-leakage.ts +112 -0
  71. package/src/rules/no-overrides-field.ts +1 -1
  72. package/src/rules/no-runtime-dependencies.test.ts +50 -0
  73. package/src/rules/no-runtime-dependencies.ts +50 -0
  74. package/src/rules/no-template-placeholders.test.ts +135 -0
  75. package/src/rules/no-template-placeholders.ts +68 -0
  76. package/src/rules/node-operation-error-itemindex.test.ts +280 -0
  77. package/src/rules/node-operation-error-itemindex.ts +223 -0
  78. package/src/rules/valid-credential-references.test.ts +230 -0
  79. package/src/rules/valid-credential-references.ts +105 -0
  80. package/src/rules/webhook-lifecycle-complete.test.ts +5 -0
  81. package/src/rules/webhook-lifecycle-complete.ts +10 -0
  82. package/src/utils/ast-utils.ts +5 -1
  83. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,280 @@
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
+ });
@@ -0,0 +1,223 @@
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
+ });