@n8n/eslint-plugin-community-nodes 0.37.0 → 0.38.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/dist/rules/no-deprecated-workflow-functions.d.ts.map +1 -1
- package/dist/rules/no-deprecated-workflow-functions.js +23 -12
- package/dist/utils/ast-utils.d.ts +8 -0
- package/dist/utils/ast-utils.d.ts.map +1 -1
- package/dist/utils/ast-utils.js +27 -0
- package/dist/utils/credential-fields.d.ts.map +1 -1
- package/dist/utils/credential-fields.js +1 -0
- package/docs/rules/no-deprecated-workflow-functions.md +20 -0
- package/package.json +4 -4
- package/src/rules/credential-password-field.test.ts +6 -0
- package/src/rules/no-deprecated-workflow-functions.test.ts +222 -0
- package/src/rules/no-deprecated-workflow-functions.ts +33 -15
- package/src/utils/ast-utils.ts +32 -0
- package/src/utils/credential-fields.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-deprecated-workflow-functions.d.ts","sourceRoot":"","sources":["../../src/rules/no-deprecated-workflow-functions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-deprecated-workflow-functions.d.ts","sourceRoot":"","sources":["../../src/rules/no-deprecated-workflow-functions.ts"],"names":[],"mappings":"AA8BA,eAAO,MAAM,iCAAiC;;CA0J5C,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
|
-
import { createRule, isThisHelpersAccess } from '../utils/index.js';
|
|
2
|
+
import { createRule, isHelpersAccess, isThisHelpersAccess, EXECUTION_CONTEXT_TYPES, } from '../utils/index.js';
|
|
3
3
|
const DEPRECATED_FUNCTIONS = {
|
|
4
4
|
request: 'httpRequest',
|
|
5
5
|
requestWithAuthentication: 'httpRequestWithAuthentication',
|
|
@@ -38,21 +38,32 @@ export const NoDeprecatedWorkflowFunctionsRule = createRule({
|
|
|
38
38
|
defaultOptions: [],
|
|
39
39
|
create(context) {
|
|
40
40
|
const n8nWorkflowTypes = new Set();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
let importsExecutionContext = false;
|
|
42
|
+
// Collect n8n-workflow imports up front. ESLint visits nodes in source order, so a
|
|
43
|
+
// `helpers` access above its import statement would otherwise be checked before the
|
|
44
|
+
// import is seen.
|
|
45
|
+
for (const statement of context.sourceCode.ast.body) {
|
|
46
|
+
if (statement.type !== AST_NODE_TYPES.ImportDeclaration ||
|
|
47
|
+
statement.source.value !== 'n8n-workflow') {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
for (const specifier of statement.specifiers) {
|
|
51
|
+
if (specifier.type === AST_NODE_TYPES.ImportSpecifier &&
|
|
52
|
+
specifier.imported.type === AST_NODE_TYPES.Identifier) {
|
|
53
|
+
n8nWorkflowTypes.add(specifier.local.name);
|
|
54
|
+
if (EXECUTION_CONTEXT_TYPES.has(specifier.imported.name)) {
|
|
55
|
+
importsExecutionContext = true;
|
|
56
|
+
}
|
|
50
57
|
}
|
|
51
|
-
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
52
61
|
MemberExpression(node) {
|
|
53
62
|
if (node.property.type === AST_NODE_TYPES.Identifier &&
|
|
54
63
|
isDeprecatedFunctionName(node.property.name)) {
|
|
55
|
-
|
|
64
|
+
// `this.helpers` is unambiguous on its own; any other receiver only counts in a file
|
|
65
|
+
// that imports an execution context type, which is what makes its `helpers` n8n's.
|
|
66
|
+
if (!isThisHelpersAccess(node) && !(importsExecutionContext && isHelpersAccess(node))) {
|
|
56
67
|
return;
|
|
57
68
|
}
|
|
58
69
|
const functionName = node.property.name;
|
|
@@ -45,6 +45,14 @@ export declare function extractCredentialNameFromArray(element: TSESTree.ArrayEx
|
|
|
45
45
|
} | null;
|
|
46
46
|
/** Matches the `this.helpers` MemberExpression (the object part of `this.helpers.foo`). */
|
|
47
47
|
export declare function isThisHelpersAccess(node: TSESTree.MemberExpression): boolean;
|
|
48
|
+
/** Execution context interfaces from `n8n-workflow` that expose a `helpers` object. */
|
|
49
|
+
export declare const EXECUTION_CONTEXT_TYPES: Set<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Matches `<receiver>.helpers.<method>` for any receiver, and a bare `helpers.<method>` left by
|
|
52
|
+
* destructuring. Deliberately does not identify the receiver: the rule runs without type
|
|
53
|
+
* information, so callers gate this on the file importing an execution context type instead.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isHelpersAccess(node: TSESTree.MemberExpression): boolean;
|
|
48
56
|
/** Matches a call expression of the form `this.methodName(...)`. */
|
|
49
57
|
export declare function isThisMethodCall(node: TSESTree.CallExpression, method: string): boolean;
|
|
50
58
|
/** Matches a call expression of the form `this.helpers.methodName(...)`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../src/utils/ast-utils.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../src/utils/ast-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/ast-utils.js
CHANGED
|
@@ -219,6 +219,33 @@ export function isThisHelpersAccess(node) {
|
|
|
219
219
|
node.object.property?.type === AST_NODE_TYPES.Identifier &&
|
|
220
220
|
node.object.property.name === 'helpers');
|
|
221
221
|
}
|
|
222
|
+
/** Execution context interfaces from `n8n-workflow` that expose a `helpers` object. */
|
|
223
|
+
export const EXECUTION_CONTEXT_TYPES = new Set([
|
|
224
|
+
'IAllExecuteFunctions',
|
|
225
|
+
'ICredentialTestFunctions',
|
|
226
|
+
'IExecuteFunctions',
|
|
227
|
+
'IExecutePaginationFunctions',
|
|
228
|
+
'IExecuteSingleFunctions',
|
|
229
|
+
'IHookFunctions',
|
|
230
|
+
'ILoadOptionsFunctions',
|
|
231
|
+
'IPollFunctions',
|
|
232
|
+
'ISupplyDataFunctions',
|
|
233
|
+
'ITriggerFunctions',
|
|
234
|
+
'IWebhookFunctions',
|
|
235
|
+
]);
|
|
236
|
+
/**
|
|
237
|
+
* Matches `<receiver>.helpers.<method>` for any receiver, and a bare `helpers.<method>` left by
|
|
238
|
+
* destructuring. Deliberately does not identify the receiver: the rule runs without type
|
|
239
|
+
* information, so callers gate this on the file importing an execution context type instead.
|
|
240
|
+
*/
|
|
241
|
+
export function isHelpersAccess(node) {
|
|
242
|
+
if (node.object.type === AST_NODE_TYPES.Identifier) {
|
|
243
|
+
return node.object.name === 'helpers';
|
|
244
|
+
}
|
|
245
|
+
return (node.object.type === AST_NODE_TYPES.MemberExpression &&
|
|
246
|
+
node.object.property.type === AST_NODE_TYPES.Identifier &&
|
|
247
|
+
node.object.property.name === 'helpers');
|
|
248
|
+
}
|
|
222
249
|
/** Matches a call expression of the form `this.methodName(...)`. */
|
|
223
250
|
export function isThisMethodCall(node, method) {
|
|
224
251
|
return (node.callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-fields.d.ts","sourceRoot":"","sources":["../../src/utils/credential-fields.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"credential-fields.d.ts","sourceRoot":"","sources":["../../src/utils/credential-fields.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAmBpD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GACvD,OAAO,CAST;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,QAAQ,CAAC,gBAAgB,GAAG,OAAO,CAWjF"}
|
|
@@ -10,6 +10,26 @@
|
|
|
10
10
|
|
|
11
11
|
Prevents usage of deprecated functions from n8n-workflow package and suggests modern alternatives.
|
|
12
12
|
|
|
13
|
+
A node reaches these helpers through its execution context, which it can hold in
|
|
14
|
+
several ways — all of them are reported:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
this.helpers.request(options); // execute() bound to the context
|
|
18
|
+
context.helpers.requestOAuth2.call(context, 'oAuth2Api', options); // context passed into a transport helper
|
|
19
|
+
this.executeFunctions.helpers.request(options); // context stored on a class field
|
|
20
|
+
const { helpers } = this;
|
|
21
|
+
helpers.request(options); // destructured off the context, reported on the call
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> [!NOTE]
|
|
25
|
+
> The rule runs without type information, so it cannot tell which object a
|
|
26
|
+
> `helpers` property belongs to. Apart from `this.helpers`, it therefore only
|
|
27
|
+
> reports in files that import an execution context interface
|
|
28
|
+
> (`IExecuteFunctions`, `ILoadOptionsFunctions`, …) from `n8n-workflow`. The
|
|
29
|
+
> check is file-scoped rather than binding-scoped: in such a file, an unrelated
|
|
30
|
+
> `someClient.helpers.request()` would also be reported. Silence the rare false
|
|
31
|
+
> positive with `// eslint-disable-next-line`.
|
|
32
|
+
|
|
13
33
|
## Examples
|
|
14
34
|
|
|
15
35
|
### ❌ Incorrect
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@n8n/eslint-plugin-community-nodes",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.38.0",
|
|
5
5
|
"main": "./dist/plugin.js",
|
|
6
6
|
"types": "./dist/plugin.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@n8n/utils": "1.
|
|
14
|
+
"@n8n/utils": "1.53.0",
|
|
15
15
|
"@typescript-eslint/typescript-estree": "8.70.0",
|
|
16
16
|
"@typescript-eslint/utils": "8.70.0",
|
|
17
17
|
"fastest-levenshtein": "1.0.16"
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"@n8n/typescript-config": "1.12.0",
|
|
21
21
|
"@n8n/vitest-config": "1.24.0",
|
|
22
22
|
"@types/node": "24.10.1",
|
|
23
|
-
"n8n-workflow": "2.
|
|
23
|
+
"n8n-workflow": "2.46.0",
|
|
24
24
|
"@typescript-eslint/rule-tester": "8.70.0",
|
|
25
25
|
"eslint-doc-generator": "^2.2.2",
|
|
26
26
|
"eslint-plugin-eslint-plugin": "^7.0.0",
|
|
27
27
|
"rimraf": "6.0.1",
|
|
28
28
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
29
29
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
30
|
-
"vite": "^8.
|
|
30
|
+
"vite": "^8.1.6",
|
|
31
31
|
"vitest": "5.0.1"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
@@ -166,6 +166,12 @@ ruleTester.run('credential-password-field', CredentialPasswordFieldRule, {
|
|
|
166
166
|
errors: [{ messageId: 'missingPasswordOption', data: { fieldName: 'apiKey' } }],
|
|
167
167
|
output: createCredentialCode([createProperty('API Key', 'apiKey', { password: true })]),
|
|
168
168
|
},
|
|
169
|
+
{
|
|
170
|
+
name: 'access key field missing typeOptions.password = true',
|
|
171
|
+
code: createCredentialCode([createProperty('Access Key', 'accessKey')]),
|
|
172
|
+
errors: [{ messageId: 'missingPasswordOption', data: { fieldName: 'accessKey' } }],
|
|
173
|
+
output: createCredentialCode([createProperty('Access Key', 'accessKey', { password: true })]),
|
|
174
|
+
},
|
|
169
175
|
{
|
|
170
176
|
name: 'secret field missing typeOptions.password = true',
|
|
171
177
|
code: createCredentialCode([createProperty('Client Secret', 'clientSecret')]),
|
|
@@ -47,6 +47,17 @@ import { IRequestOptions } from 'some-other-package';
|
|
|
47
47
|
|
|
48
48
|
function test(options: IRequestOptions) {
|
|
49
49
|
return options.url;
|
|
50
|
+
}`,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'non-this helpers in a file that does not handle execution contexts',
|
|
54
|
+
code: `
|
|
55
|
+
import { SomeApiClient } from 'some-other-package';
|
|
56
|
+
|
|
57
|
+
function makeRequest(client: SomeApiClient) {
|
|
58
|
+
const { helpers } = client;
|
|
59
|
+
helpers.request('https://example.com');
|
|
60
|
+
return client.helpers.request('https://example.com');
|
|
50
61
|
}`,
|
|
51
62
|
},
|
|
52
63
|
],
|
|
@@ -183,5 +194,216 @@ return this.helpers.prepareOutputData([{ json: response }]);`,
|
|
|
183
194
|
},
|
|
184
195
|
],
|
|
185
196
|
},
|
|
197
|
+
{
|
|
198
|
+
name: 'execution context passed in as a parameter',
|
|
199
|
+
code: `
|
|
200
|
+
import type { IExecuteFunctions } from 'n8n-workflow';
|
|
201
|
+
|
|
202
|
+
export async function apiRequest(context: IExecuteFunctions, options) {
|
|
203
|
+
return await context.helpers.requestOAuth2.call(context, 'exampleOAuth2Api', options);
|
|
204
|
+
}`,
|
|
205
|
+
errors: [
|
|
206
|
+
{
|
|
207
|
+
messageId: 'deprecatedRequestFunction',
|
|
208
|
+
data: { functionName: 'requestOAuth2', replacement: 'httpRequestWithAuthentication' },
|
|
209
|
+
suggestions: [
|
|
210
|
+
{
|
|
211
|
+
messageId: 'suggestReplaceFunction',
|
|
212
|
+
data: { functionName: 'requestOAuth2', replacement: 'httpRequestWithAuthentication' },
|
|
213
|
+
output: `
|
|
214
|
+
import type { IExecuteFunctions } from 'n8n-workflow';
|
|
215
|
+
|
|
216
|
+
export async function apiRequest(context: IExecuteFunctions, options) {
|
|
217
|
+
return await context.helpers.httpRequestWithAuthentication.call(context, 'exampleOAuth2Api', options);
|
|
218
|
+
}`,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'execution context union type parameter',
|
|
226
|
+
code: `
|
|
227
|
+
import type { IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-workflow';
|
|
228
|
+
|
|
229
|
+
async function loadOptions(ctx: IExecuteFunctions | ILoadOptionsFunctions) {
|
|
230
|
+
return await ctx.helpers.request({ url: 'https://example.com' });
|
|
231
|
+
}`,
|
|
232
|
+
errors: [
|
|
233
|
+
{
|
|
234
|
+
messageId: 'deprecatedRequestFunction',
|
|
235
|
+
data: { functionName: 'request', replacement: 'httpRequest' },
|
|
236
|
+
suggestions: [
|
|
237
|
+
{
|
|
238
|
+
messageId: 'suggestReplaceFunction',
|
|
239
|
+
data: { functionName: 'request', replacement: 'httpRequest' },
|
|
240
|
+
output: `
|
|
241
|
+
import type { IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-workflow';
|
|
242
|
+
|
|
243
|
+
async function loadOptions(ctx: IExecuteFunctions | ILoadOptionsFunctions) {
|
|
244
|
+
return await ctx.helpers.httpRequest({ url: 'https://example.com' });
|
|
245
|
+
}`,
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: 'helpers destructured from this',
|
|
253
|
+
code: `
|
|
254
|
+
import type { IExecuteFunctions } from 'n8n-workflow';
|
|
255
|
+
|
|
256
|
+
async function run(this: IExecuteFunctions) {
|
|
257
|
+
const { helpers } = this;
|
|
258
|
+
return await helpers.requestWithAuthentication('exampleApi', options);
|
|
259
|
+
}`,
|
|
260
|
+
errors: [
|
|
261
|
+
{
|
|
262
|
+
messageId: 'deprecatedRequestFunction',
|
|
263
|
+
data: {
|
|
264
|
+
functionName: 'requestWithAuthentication',
|
|
265
|
+
replacement: 'httpRequestWithAuthentication',
|
|
266
|
+
},
|
|
267
|
+
suggestions: [
|
|
268
|
+
{
|
|
269
|
+
messageId: 'suggestReplaceFunction',
|
|
270
|
+
data: {
|
|
271
|
+
functionName: 'requestWithAuthentication',
|
|
272
|
+
replacement: 'httpRequestWithAuthentication',
|
|
273
|
+
},
|
|
274
|
+
output: `
|
|
275
|
+
import type { IExecuteFunctions } from 'n8n-workflow';
|
|
276
|
+
|
|
277
|
+
async function run(this: IExecuteFunctions) {
|
|
278
|
+
const { helpers } = this;
|
|
279
|
+
return await helpers.httpRequestWithAuthentication('exampleApi', options);
|
|
280
|
+
}`,
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'execution context held in a class field',
|
|
288
|
+
code: `
|
|
289
|
+
import type { IExecuteFunctions } from 'n8n-workflow';
|
|
290
|
+
|
|
291
|
+
class ExampleApi {
|
|
292
|
+
private executeFunctions: IExecuteFunctions;
|
|
293
|
+
|
|
294
|
+
async request(options) {
|
|
295
|
+
return await this.executeFunctions.helpers.requestWithAuthentication.call(
|
|
296
|
+
this.executeFunctions,
|
|
297
|
+
'exampleApi',
|
|
298
|
+
options,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}`,
|
|
302
|
+
errors: [
|
|
303
|
+
{
|
|
304
|
+
messageId: 'deprecatedRequestFunction',
|
|
305
|
+
data: {
|
|
306
|
+
functionName: 'requestWithAuthentication',
|
|
307
|
+
replacement: 'httpRequestWithAuthentication',
|
|
308
|
+
},
|
|
309
|
+
suggestions: [
|
|
310
|
+
{
|
|
311
|
+
messageId: 'suggestReplaceFunction',
|
|
312
|
+
data: {
|
|
313
|
+
functionName: 'requestWithAuthentication',
|
|
314
|
+
replacement: 'httpRequestWithAuthentication',
|
|
315
|
+
},
|
|
316
|
+
output: `
|
|
317
|
+
import type { IExecuteFunctions } from 'n8n-workflow';
|
|
318
|
+
|
|
319
|
+
class ExampleApi {
|
|
320
|
+
private executeFunctions: IExecuteFunctions;
|
|
321
|
+
|
|
322
|
+
async request(options) {
|
|
323
|
+
return await this.executeFunctions.helpers.httpRequestWithAuthentication.call(
|
|
324
|
+
this.executeFunctions,
|
|
325
|
+
'exampleApi',
|
|
326
|
+
options,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}`,
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: 'execution context declared as a constructor parameter property',
|
|
337
|
+
code: `
|
|
338
|
+
import type { IExecuteFunctions } from 'n8n-workflow';
|
|
339
|
+
|
|
340
|
+
class ExampleApi {
|
|
341
|
+
constructor(private readonly ctx: IExecuteFunctions) {}
|
|
342
|
+
|
|
343
|
+
async run() {
|
|
344
|
+
return await this.ctx.helpers.prepareOutputData([]);
|
|
345
|
+
}
|
|
346
|
+
}`,
|
|
347
|
+
errors: [
|
|
348
|
+
{
|
|
349
|
+
messageId: 'deprecatedWithoutReplacement',
|
|
350
|
+
data: { functionName: 'prepareOutputData' },
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: 'execution context used above its import statement',
|
|
356
|
+
code: `
|
|
357
|
+
async function apiRequest(context: IExecuteFunctions, options) {
|
|
358
|
+
return await context.helpers.request(options);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
import type { IExecuteFunctions } from 'n8n-workflow';`,
|
|
362
|
+
errors: [
|
|
363
|
+
{
|
|
364
|
+
messageId: 'deprecatedRequestFunction',
|
|
365
|
+
data: { functionName: 'request', replacement: 'httpRequest' },
|
|
366
|
+
suggestions: [
|
|
367
|
+
{
|
|
368
|
+
messageId: 'suggestReplaceFunction',
|
|
369
|
+
data: { functionName: 'request', replacement: 'httpRequest' },
|
|
370
|
+
output: `
|
|
371
|
+
async function apiRequest(context: IExecuteFunctions, options) {
|
|
372
|
+
return await context.helpers.httpRequest(options);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
import type { IExecuteFunctions } from 'n8n-workflow';`,
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: 'credential test context passed in as a parameter',
|
|
383
|
+
code: `
|
|
384
|
+
import type { ICredentialTestFunctions } from 'n8n-workflow';
|
|
385
|
+
|
|
386
|
+
export async function testCredential(context: ICredentialTestFunctions, options) {
|
|
387
|
+
return await context.helpers.request(options);
|
|
388
|
+
}`,
|
|
389
|
+
errors: [
|
|
390
|
+
{
|
|
391
|
+
messageId: 'deprecatedRequestFunction',
|
|
392
|
+
data: { functionName: 'request', replacement: 'httpRequest' },
|
|
393
|
+
suggestions: [
|
|
394
|
+
{
|
|
395
|
+
messageId: 'suggestReplaceFunction',
|
|
396
|
+
data: { functionName: 'request', replacement: 'httpRequest' },
|
|
397
|
+
output: `
|
|
398
|
+
import type { ICredentialTestFunctions } from 'n8n-workflow';
|
|
399
|
+
|
|
400
|
+
export async function testCredential(context: ICredentialTestFunctions, options) {
|
|
401
|
+
return await context.helpers.httpRequest(options);
|
|
402
|
+
}`,
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
},
|
|
186
408
|
],
|
|
187
409
|
});
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createRule,
|
|
5
|
+
isHelpersAccess,
|
|
6
|
+
isThisHelpersAccess,
|
|
7
|
+
EXECUTION_CONTEXT_TYPES,
|
|
8
|
+
} from '../utils/index.js';
|
|
4
9
|
|
|
5
10
|
const DEPRECATED_FUNCTIONS = {
|
|
6
11
|
request: 'httpRequest',
|
|
@@ -46,27 +51,40 @@ export const NoDeprecatedWorkflowFunctionsRule = createRule({
|
|
|
46
51
|
defaultOptions: [],
|
|
47
52
|
create(context) {
|
|
48
53
|
const n8nWorkflowTypes = new Set<string>();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
let importsExecutionContext = false;
|
|
55
|
+
|
|
56
|
+
// Collect n8n-workflow imports up front. ESLint visits nodes in source order, so a
|
|
57
|
+
// `helpers` access above its import statement would otherwise be checked before the
|
|
58
|
+
// import is seen.
|
|
59
|
+
for (const statement of context.sourceCode.ast.body) {
|
|
60
|
+
if (
|
|
61
|
+
statement.type !== AST_NODE_TYPES.ImportDeclaration ||
|
|
62
|
+
statement.source.value !== 'n8n-workflow'
|
|
63
|
+
) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
for (const specifier of statement.specifiers) {
|
|
67
|
+
if (
|
|
68
|
+
specifier.type === AST_NODE_TYPES.ImportSpecifier &&
|
|
69
|
+
specifier.imported.type === AST_NODE_TYPES.Identifier
|
|
70
|
+
) {
|
|
71
|
+
n8nWorkflowTypes.add(specifier.local.name);
|
|
72
|
+
if (EXECUTION_CONTEXT_TYPES.has(specifier.imported.name)) {
|
|
73
|
+
importsExecutionContext = true;
|
|
74
|
+
}
|
|
61
75
|
}
|
|
62
|
-
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
63
78
|
|
|
79
|
+
return {
|
|
64
80
|
MemberExpression(node) {
|
|
65
81
|
if (
|
|
66
82
|
node.property.type === AST_NODE_TYPES.Identifier &&
|
|
67
83
|
isDeprecatedFunctionName(node.property.name)
|
|
68
84
|
) {
|
|
69
|
-
|
|
85
|
+
// `this.helpers` is unambiguous on its own; any other receiver only counts in a file
|
|
86
|
+
// that imports an execution context type, which is what makes its `helpers` n8n's.
|
|
87
|
+
if (!isThisHelpersAccess(node) && !(importsExecutionContext && isHelpersAccess(node))) {
|
|
70
88
|
return;
|
|
71
89
|
}
|
|
72
90
|
|
package/src/utils/ast-utils.ts
CHANGED
|
@@ -327,6 +327,38 @@ export function isThisHelpersAccess(node: TSESTree.MemberExpression): boolean {
|
|
|
327
327
|
);
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
/** Execution context interfaces from `n8n-workflow` that expose a `helpers` object. */
|
|
331
|
+
export const EXECUTION_CONTEXT_TYPES = new Set([
|
|
332
|
+
'IAllExecuteFunctions',
|
|
333
|
+
'ICredentialTestFunctions',
|
|
334
|
+
'IExecuteFunctions',
|
|
335
|
+
'IExecutePaginationFunctions',
|
|
336
|
+
'IExecuteSingleFunctions',
|
|
337
|
+
'IHookFunctions',
|
|
338
|
+
'ILoadOptionsFunctions',
|
|
339
|
+
'IPollFunctions',
|
|
340
|
+
'ISupplyDataFunctions',
|
|
341
|
+
'ITriggerFunctions',
|
|
342
|
+
'IWebhookFunctions',
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Matches `<receiver>.helpers.<method>` for any receiver, and a bare `helpers.<method>` left by
|
|
347
|
+
* destructuring. Deliberately does not identify the receiver: the rule runs without type
|
|
348
|
+
* information, so callers gate this on the file importing an execution context type instead.
|
|
349
|
+
*/
|
|
350
|
+
export function isHelpersAccess(node: TSESTree.MemberExpression): boolean {
|
|
351
|
+
if (node.object.type === AST_NODE_TYPES.Identifier) {
|
|
352
|
+
return node.object.name === 'helpers';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
node.object.type === AST_NODE_TYPES.MemberExpression &&
|
|
357
|
+
node.object.property.type === AST_NODE_TYPES.Identifier &&
|
|
358
|
+
node.object.property.name === 'helpers'
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
330
362
|
/** Matches a call expression of the form `this.methodName(...)`. */
|
|
331
363
|
export function isThisMethodCall(node: TSESTree.CallExpression, method: string): boolean {
|
|
332
364
|
return (
|