mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
feat(core): Add require-node-api-error ESLint rule for community nodes (no-changelog) (#28454)
This commit is contained in:
parent
cb1244c041
commit
fc5424477d
8 changed files with 386 additions and 27 deletions
|
|
@ -41,30 +41,32 @@ export default [
|
|||
✅ Set in the `recommended` configuration.\
|
||||
☑️ Set in the `recommendedWithoutN8nCloudSupport` configuration.\
|
||||
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
|
||||
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
||||
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\
|
||||
❌ Deprecated.
|
||||
|
||||
| Name | Description | 💼 | ⚠️ | 🔧 | 💡 |
|
||||
| :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | :--- | :--- | :- | :- |
|
||||
| [ai-node-package-json](docs/rules/ai-node-package-json.md) | Enforce consistency between n8n.aiNodeSdkVersion and ai-node-sdk peer dependency in community node packages | ✅ ☑️ | | | |
|
||||
| [cred-class-field-icon-missing](docs/rules/cred-class-field-icon-missing.md) | Credential class must have an `icon` property defined | ✅ ☑️ | | | 💡 |
|
||||
| [credential-documentation-url](docs/rules/credential-documentation-url.md) | Enforce valid credential documentationUrl format (URL or lowercase alphanumeric slug) | ✅ ☑️ | | 🔧 | |
|
||||
| [credential-password-field](docs/rules/credential-password-field.md) | Ensure credential fields with sensitive names have typeOptions.password = true | ✅ ☑️ | | 🔧 | |
|
||||
| [credential-test-required](docs/rules/credential-test-required.md) | Ensure credentials have a credential test | ✅ ☑️ | | | 💡 |
|
||||
| [icon-validation](docs/rules/icon-validation.md) | Validate node and credential icon files exist, are SVG format, and light/dark icons are different | ✅ ☑️ | | | 💡 |
|
||||
| [missing-paired-item](docs/rules/missing-paired-item.md) | Require pairedItem on INodeExecutionData objects in execute() methods to preserve item linking. | ✅ ☑️ | | | |
|
||||
| [no-credential-reuse](docs/rules/no-credential-reuse.md) | Prevent credential re-use security issues by ensuring nodes only reference credentials from the same package | ✅ ☑️ | | | 💡 |
|
||||
| [no-deprecated-workflow-functions](docs/rules/no-deprecated-workflow-functions.md) | Disallow usage of deprecated functions and types from n8n-workflow package | ✅ ☑️ | | | 💡 |
|
||||
| [no-forbidden-lifecycle-scripts](docs/rules/no-forbidden-lifecycle-scripts.md) | Ban lifecycle scripts (prepare, preinstall, postinstall, etc.) in community node packages | ✅ ☑️ | | | |
|
||||
| [no-http-request-with-manual-auth](docs/rules/no-http-request-with-manual-auth.md) | Disallow this.helpers.httpRequest() in functions that call this.getCredentials(). Use this.helpers.httpRequestWithAuthentication() instead. | ✅ ☑️ | | | |
|
||||
| [no-restricted-globals](docs/rules/no-restricted-globals.md) | Disallow usage of restricted global variables in community nodes. | ✅ | | | |
|
||||
| [no-restricted-imports](docs/rules/no-restricted-imports.md) | Disallow usage of restricted imports in community nodes. | ✅ | | | |
|
||||
| [node-class-description-icon-missing](docs/rules/node-class-description-icon-missing.md) | **Deprecated.** Node class description must have an `icon` property defined. Use `require-node-description-fields` instead. | ✅ ☑️ | | | 💡 |
|
||||
| [node-connection-type-literal](docs/rules/node-connection-type-literal.md) | Disallow string literals in node description `inputs`/`outputs` — use `NodeConnectionTypes` enum instead | ✅ ☑️ | | 🔧 | |
|
||||
| [node-usable-as-tool](docs/rules/node-usable-as-tool.md) | Ensure node classes have usableAsTool property | ✅ ☑️ | | 🔧 | |
|
||||
| [options-sorted-alphabetically](docs/rules/options-sorted-alphabetically.md) | Enforce alphabetical ordering of options arrays in n8n node properties | | ✅ ☑️ | | |
|
||||
| [package-name-convention](docs/rules/package-name-convention.md) | Enforce correct package naming convention for n8n community nodes | ✅ ☑️ | | | 💡 |
|
||||
| [require-continue-on-fail](docs/rules/require-continue-on-fail.md) | Require continueOnFail() handling in execute() methods of node classes | ✅ ☑️ | | | |
|
||||
| [require-node-description-fields](docs/rules/require-node-description-fields.md) | Node class description must define all required fields | ✅ ☑️ | | | |
|
||||
| [resource-operation-pattern](docs/rules/resource-operation-pattern.md) | Enforce proper resource/operation pattern for better UX in n8n nodes | | ✅ ☑️ | | |
|
||||
| Name | Description | 💼 | ⚠️ | 🔧 | 💡 | ❌ |
|
||||
| :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | :--- | :--- | :- | :- | :- |
|
||||
| [ai-node-package-json](docs/rules/ai-node-package-json.md) | Enforce consistency between n8n.aiNodeSdkVersion and ai-node-sdk peer dependency in community node packages | ✅ ☑️ | | | | |
|
||||
| [cred-class-field-icon-missing](docs/rules/cred-class-field-icon-missing.md) | Credential class must have an `icon` property defined | ✅ ☑️ | | | 💡 | |
|
||||
| [credential-documentation-url](docs/rules/credential-documentation-url.md) | Enforce valid credential documentationUrl format (URL or lowercase alphanumeric slug) | ✅ ☑️ | | 🔧 | | |
|
||||
| [credential-password-field](docs/rules/credential-password-field.md) | Ensure credential fields with sensitive names have typeOptions.password = true | ✅ ☑️ | | 🔧 | | |
|
||||
| [credential-test-required](docs/rules/credential-test-required.md) | Ensure credentials have a credential test | ✅ ☑️ | | | 💡 | |
|
||||
| [icon-validation](docs/rules/icon-validation.md) | Validate node and credential icon files exist, are SVG format, and light/dark icons are different | ✅ ☑️ | | | 💡 | |
|
||||
| [missing-paired-item](docs/rules/missing-paired-item.md) | Require pairedItem on INodeExecutionData objects in execute() methods to preserve item linking. | ✅ ☑️ | | | | |
|
||||
| [no-credential-reuse](docs/rules/no-credential-reuse.md) | Prevent credential re-use security issues by ensuring nodes only reference credentials from the same package | ✅ ☑️ | | | 💡 | |
|
||||
| [no-deprecated-workflow-functions](docs/rules/no-deprecated-workflow-functions.md) | Disallow usage of deprecated functions and types from n8n-workflow package | ✅ ☑️ | | | 💡 | |
|
||||
| [no-forbidden-lifecycle-scripts](docs/rules/no-forbidden-lifecycle-scripts.md) | Ban lifecycle scripts (prepare, preinstall, postinstall, etc.) in community node packages | ✅ ☑️ | | | | |
|
||||
| [no-http-request-with-manual-auth](docs/rules/no-http-request-with-manual-auth.md) | Disallow this.helpers.httpRequest() in functions that call this.getCredentials(). Use this.helpers.httpRequestWithAuthentication() instead. | ✅ ☑️ | | | | |
|
||||
| [no-restricted-globals](docs/rules/no-restricted-globals.md) | Disallow usage of restricted global variables in community nodes. | ✅ | | | | |
|
||||
| [no-restricted-imports](docs/rules/no-restricted-imports.md) | Disallow usage of restricted imports in community nodes. | ✅ | | | | |
|
||||
| [node-class-description-icon-missing](docs/rules/node-class-description-icon-missing.md) | Node class description must have an `icon` property defined. Deprecated: use `require-node-description-fields` instead. | | | | 💡 | ❌ |
|
||||
| [node-connection-type-literal](docs/rules/node-connection-type-literal.md) | Disallow string literals in node description `inputs`/`outputs` — use `NodeConnectionTypes` enum instead | ✅ ☑️ | | 🔧 | | |
|
||||
| [node-usable-as-tool](docs/rules/node-usable-as-tool.md) | Ensure node classes have usableAsTool property | ✅ ☑️ | | 🔧 | | |
|
||||
| [options-sorted-alphabetically](docs/rules/options-sorted-alphabetically.md) | Enforce alphabetical ordering of options arrays in n8n node properties | | ✅ ☑️ | | | |
|
||||
| [package-name-convention](docs/rules/package-name-convention.md) | Enforce correct package naming convention for n8n community nodes | ✅ ☑️ | | | 💡 | |
|
||||
| [require-continue-on-fail](docs/rules/require-continue-on-fail.md) | Require continueOnFail() handling in execute() methods of node classes | ✅ ☑️ | | | | |
|
||||
| [require-node-api-error](docs/rules/require-node-api-error.md) | Require NodeApiError or NodeOperationError for error wrapping in catch blocks. Raw errors lose HTTP context in the n8n UI. | ✅ ☑️ | | | | |
|
||||
| [require-node-description-fields](docs/rules/require-node-description-fields.md) | Node class description must define all required fields: icon, subtitle | ✅ ☑️ | | | | |
|
||||
| [resource-operation-pattern](docs/rules/resource-operation-pattern.md) | Enforce proper resource/operation pattern for better UX in n8n nodes | | ✅ ☑️ | | | |
|
||||
|
||||
<!-- end auto-generated rules list -->
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
# Node class description must have an `icon` property defined (`@n8n/community-nodes/node-class-description-icon-missing`)
|
||||
# Node class description must have an `icon` property defined. Deprecated: use `require-node-description-fields` instead (`@n8n/community-nodes/node-class-description-icon-missing`)
|
||||
|
||||
❌ This rule is **deprecated**. Use [`require-node-description-fields`](require-node-description-fields.md) instead.
|
||||
❌ This rule is deprecated.
|
||||
|
||||
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
||||
|
||||
<!-- end auto-generated rule header -->
|
||||
|
||||
> **Deprecated:** Use [`require-node-description-fields`](require-node-description-fields.md) instead.
|
||||
|
||||
## Rule Details
|
||||
|
||||
Validates that node classes define an `icon` property in their `description` object. Icons are required for nodes to display correctly in the n8n editor.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
# Require NodeApiError or NodeOperationError for error wrapping in catch blocks. Raw errors lose HTTP context in the n8n UI (`@n8n/community-nodes/require-node-api-error`)
|
||||
|
||||
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
||||
|
||||
<!-- end auto-generated rule header -->
|
||||
|
||||
## Rule Details
|
||||
|
||||
When errors are caught and re-thrown in n8n nodes, they must be wrapped in
|
||||
`NodeApiError` or `NodeOperationError`. Raw re-throws and generic `Error`
|
||||
constructors lose HTTP context (status code, response body, etc.) that the n8n
|
||||
UI relies on to display meaningful error information to users.
|
||||
|
||||
## Examples
|
||||
|
||||
### Incorrect
|
||||
|
||||
```js
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
throw new Error('Request failed');
|
||||
}
|
||||
```
|
||||
|
||||
### Correct
|
||||
|
||||
```js
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(this.getNode(), 'Operation failed', { itemIndex: i });
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { error: error.message } });
|
||||
continue;
|
||||
}
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
```
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Node class description must define all required fields (`@n8n/community-nodes/require-node-description-fields`)
|
||||
# Node class description must define all required fields: icon, subtitle (`@n8n/community-nodes/require-node-description-fields`)
|
||||
|
||||
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ const configs = {
|
|||
'@n8n/community-nodes/missing-paired-item': 'error',
|
||||
'@n8n/community-nodes/require-community-node-keyword': 'warn',
|
||||
'@n8n/community-nodes/require-continue-on-fail': 'error',
|
||||
'@n8n/community-nodes/require-node-api-error': 'error',
|
||||
'@n8n/community-nodes/require-node-description-fields': 'error',
|
||||
},
|
||||
},
|
||||
|
|
@ -67,6 +68,7 @@ const configs = {
|
|||
'@n8n/community-nodes/missing-paired-item': 'error',
|
||||
'@n8n/community-nodes/require-community-node-keyword': 'warn',
|
||||
'@n8n/community-nodes/require-continue-on-fail': 'error',
|
||||
'@n8n/community-nodes/require-node-api-error': 'error',
|
||||
'@n8n/community-nodes/require-node-description-fields': 'error',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { OptionsSortedAlphabeticallyRule } from './options-sorted-alphabetically
|
|||
import { PackageNameConventionRule } from './package-name-convention.js';
|
||||
import { RequireCommunityNodeKeywordRule } from './require-community-node-keyword.js';
|
||||
import { RequireContinueOnFailRule } from './require-continue-on-fail.js';
|
||||
import { RequireNodeApiErrorRule } from './require-node-api-error.js';
|
||||
import { RequireNodeDescriptionFieldsRule } from './require-node-description-fields.js';
|
||||
import { ResourceOperationPatternRule } from './resource-operation-pattern.js';
|
||||
|
||||
|
|
@ -45,5 +46,6 @@ export const rules = {
|
|||
'missing-paired-item': MissingPairedItemRule,
|
||||
'require-community-node-keyword': RequireCommunityNodeKeywordRule,
|
||||
'require-continue-on-fail': RequireContinueOnFailRule,
|
||||
'require-node-api-error': RequireNodeApiErrorRule,
|
||||
'require-node-description-fields': RequireNodeDescriptionFieldsRule,
|
||||
} satisfies Record<string, AnyRuleModule>;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,199 @@
|
|||
import { RuleTester } from '@typescript-eslint/rule-tester';
|
||||
|
||||
import { RequireNodeApiErrorRule } from './require-node-api-error.js';
|
||||
|
||||
const ruleTester = new RuleTester();
|
||||
|
||||
ruleTester.run('require-node-api-error', RequireNodeApiErrorRule, {
|
||||
valid: [
|
||||
{
|
||||
name: 'throw NodeApiError in catch block',
|
||||
code: `
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'throw NodeOperationError in catch block',
|
||||
code: `
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(this.getNode(), 'Operation failed', { itemIndex: i });
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'throw outside catch block (not in scope)',
|
||||
code: `
|
||||
function validate(input: string) {
|
||||
if (!input) {
|
||||
throw new Error('Input required');
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'throw new Error outside catch block (not in scope)',
|
||||
code: `
|
||||
throw new Error('Something went wrong');`,
|
||||
},
|
||||
{
|
||||
name: 'continueOnFail pattern with NodeApiError',
|
||||
code: `
|
||||
try {
|
||||
responseData = await apiRequest.call(this, 'POST', '/tasks', body);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { error: error.message } });
|
||||
continue;
|
||||
}
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'conditional handling then NodeApiError in else',
|
||||
code: `
|
||||
try {
|
||||
await ftp.put(data, path);
|
||||
} catch (error) {
|
||||
if (error.code === 553) {
|
||||
await ftp.mkdir(dirPath, true);
|
||||
await ftp.put(data, path);
|
||||
} else {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'throw wrapped error stored in variable',
|
||||
code: `
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
const wrapped = new NodeApiError(this.getNode(), error as JsonObject);
|
||||
throw wrapped;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'shadowed variable with same name as catch param',
|
||||
code: `
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
const fn = (error: Error) => {
|
||||
throw error;
|
||||
};
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'no throw in catch block',
|
||||
code: `
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'bare re-throw in credential file (skipped)',
|
||||
filename: '/path/to/MyCredential.credentials.ts',
|
||||
code: `
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'bare re-throw in .js file (skipped)',
|
||||
filename: '/path/to/helper.js',
|
||||
code: `
|
||||
try {
|
||||
apiRequest();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
name: 'bare re-throw of caught error',
|
||||
code: `
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}`,
|
||||
errors: [{ messageId: 'useNodeApiError' }],
|
||||
},
|
||||
{
|
||||
name: 'throw new Error in catch block',
|
||||
code: `
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (error) {
|
||||
throw new Error('Request failed');
|
||||
}`,
|
||||
errors: [
|
||||
{
|
||||
messageId: 'useNodeApiErrorInsteadOfGeneric',
|
||||
data: { errorClass: 'Error' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'bare re-throw after continueOnFail',
|
||||
code: `
|
||||
try {
|
||||
responseData = await apiRequest.call(this, 'POST', '/tasks', body);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { error: error.message } });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}`,
|
||||
errors: [{ messageId: 'useNodeApiError' }],
|
||||
},
|
||||
{
|
||||
name: 'throw new TypeError in catch block',
|
||||
code: `
|
||||
try {
|
||||
JSON.parse(data);
|
||||
} catch (error) {
|
||||
throw new TypeError('Invalid JSON');
|
||||
}`,
|
||||
errors: [
|
||||
{
|
||||
messageId: 'useNodeApiErrorInsteadOfGeneric',
|
||||
data: { errorClass: 'TypeError' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'bare re-throw in nested catch',
|
||||
code: `
|
||||
try {
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (innerError) {
|
||||
throw innerError;
|
||||
}
|
||||
} catch (outerError) {
|
||||
throw new NodeApiError(this.getNode(), outerError as JsonObject);
|
||||
}`,
|
||||
errors: [{ messageId: 'useNodeApiError' }],
|
||||
},
|
||||
{
|
||||
name: 'throw named variable in catch',
|
||||
code: `
|
||||
try {
|
||||
await apiRequest();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}`,
|
||||
errors: [{ messageId: 'useNodeApiError' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import { DefinitionType } from '@typescript-eslint/scope-manager';
|
||||
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
|
||||
|
||||
import { isFileType } from '../utils/index.js';
|
||||
import { createRule } from '../utils/rule-creator.js';
|
||||
|
||||
const ALLOWED_ERROR_CLASSES = new Set(['NodeApiError', 'NodeOperationError']);
|
||||
|
||||
function getThrowCalleeName(argument: TSESTree.Expression): string | null {
|
||||
if (argument.type === AST_NODE_TYPES.NewExpression) {
|
||||
if (argument.callee.type === AST_NODE_TYPES.Identifier) {
|
||||
return argument.callee.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isInsideCatchClause(node: TSESTree.Node): boolean {
|
||||
let current: TSESTree.Node | undefined = node.parent;
|
||||
while (current) {
|
||||
if (current.type === AST_NODE_TYPES.CatchClause) {
|
||||
return true;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const RequireNodeApiErrorRule = createRule({
|
||||
name: 'require-node-api-error',
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
'Require NodeApiError or NodeOperationError for error wrapping in catch blocks. ' +
|
||||
'Raw errors lose HTTP context in the n8n UI.',
|
||||
},
|
||||
messages: {
|
||||
useNodeApiError:
|
||||
'Use `NodeApiError` or `NodeOperationError` instead of re-throwing raw errors. ' +
|
||||
'Example: `throw new NodeApiError(this.getNode(), error as JsonObject)`',
|
||||
useNodeApiErrorInsteadOfGeneric:
|
||||
'Use `NodeApiError` or `NodeOperationError` instead of `{{ errorClass }}`. ' +
|
||||
'Example: `throw new NodeApiError(this.getNode(), error as JsonObject)`',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
const isNodeFile = isFileType(context.filename, '.node.ts');
|
||||
const isHelperFile =
|
||||
context.filename.endsWith('.ts') &&
|
||||
!isNodeFile &&
|
||||
!isFileType(context.filename, '.credentials.ts');
|
||||
|
||||
if (!isNodeFile && !isHelperFile) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
ThrowStatement(node) {
|
||||
if (!isInsideCatchClause(node)) return;
|
||||
if (!node.argument) return;
|
||||
|
||||
const { argument } = node;
|
||||
|
||||
if (argument.type === AST_NODE_TYPES.Identifier) {
|
||||
const scope = context.sourceCode.getScope(node);
|
||||
const ref = scope.references.find((r) => r.identifier === argument);
|
||||
const isCatchParam =
|
||||
ref?.resolved?.defs.some((def) => def.type === DefinitionType.CatchClause) ?? false;
|
||||
|
||||
if (isCatchParam) {
|
||||
context.report({ node, messageId: 'useNodeApiError' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const calleeName = getThrowCalleeName(argument);
|
||||
if (calleeName !== null && !ALLOWED_ERROR_CLASSES.has(calleeName)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'useNodeApiErrorInsteadOfGeneric',
|
||||
data: { errorClass: calleeName },
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
Loading…
Reference in a new issue